Rooftop Ruby Podcast
Two Ruby programmers (Collin Donnell and Joel Drapper) discuss Ruby, web and native software development, technology, and more.
Rooftop Ruby Podcast
9: Dry-RB and Hanami with Tim Riley
Tim Riley joins to talk about dry-rb and the Hanami web framework.
Follow us on Mastodon:
Show art created by JD Davis.
Hello, welcome to the show. As always. I'm one of your hosts, Collin. Today we are joined by a very special guest, Tim Riley. Listeners may know Tim from such projects as dry-rb and Hanami. Tim, welcome to the rooftop.
Tim:Hi. It's great to be here.
Collin:Joel is also here. Hello, Joel.
Joel:Hey, Collin. Hey, Tim.
Collin:So, uh, just starting off with these two projects, I read the website for dry-rb and it's dry-rb helps you write clear, Phlexible, more maintainable ruby code. In your own words, what is dry-rb?
Tim:Well, I wrote those words, so they are, they are my own. Uh, that, that really summarizes it in a nutshell, I suppose. Like if, if you, um, if you've ever felt the need to make something a little clearer, more testable, more reusable, inside your Ruby code bases, then dry-rb, may have a library to help you. It started off small as a collection, but now we. Some 20 or more different libraries. The, the dry-rbeing don't repeat yourself. Uh, so we've tried to take that as an ethos and build those small things that you find you need and put them into a gem that can be used by any kind of application, and try and improve the Ruby ecosystem from all corners.
Collin:Yeah. You can actually just sort of pick and choose based on what you need, right? Is that correct?
Tim:Yeah, there are, I guess there are a few headline gems that we feature on the top of the homepage, but there, there are, uh, 20 plus each doing different things. But yes, if we just look at one of the, probably our most used. Uh, maybe our most well known gem dry-validation, the idea for that is that if you accept some kind of user input and you want to make sure is valid based on a range of both type, structural and business rules, uh, this is a gem that can take care of that for you. That isn't tied to any sort of persistence framework, it isn't tied to any sort of application framework. Instead, it's something that is small and can be dropped into whatever context you might need inside your Ruby app.
Collin:Right. So I was looking at this and it seems like you subclass dry-validation contract and then you can specify required properties, determine if things are correct, uh, sort of set that up. So is that kind of what a contract is?
Tim:Yes, that's right.
Collin:Where did the inspiration for that come from? Cause I've heard about this kind of thing in other languages.
Tim:Well, dry-validation was actually the, the gem that started it all for us. Uh, it was the first, the first dry-rb gem and the inspiration, uh, would've come from. So the person who started dry-validation, his name is Andy Holland, based in the UK now in dub, now in Western Australia, actually. I can't speak to his initial motivation because he started this like a few months before I sort of got involved in the project, but my feeling is, as we all know, when we work with Ruby on Rails, the, the dominant application framework for Ruby, the validation tools that are given to us are bound to this class that deals with the persistence of those records to a database. And that can make for some hard to manage scenarios where You have quite distinct validation requirements for one context versus another, and dry-validation decouples that, and it lets you model those requirements precisely and do so in a way that lets them be used in any part of your application flow. And from there, it's grown in capabilities, and now it's a really powerful standalone tool.
Collin:Right. So you could have this just as like part of your controller, you know, handler methods. You could have this somewhere else, anywhere, right?
Tim:Yes, that's right. It's, uh, you know, you can consider parts of dry-validation, for instance, as a replacement for the simple structural checks that you do for incoming params on your HTTP request. But then you might also layer on top much more involved business logic validation on top of those basic structures and types as part of your main application operational code, whether that's a command class, or a service class, or whether you put it in your controller or, or over, uh, but it gives you a way to express validations quite fluently and sort of tie in arbitrary ruby code as well to express them in the way you need.
Collin:Something I had a question about with this, and maybe Joel also has thoughts about this, which is it seems as though you create your contract class and then you basically can ask the contract by giving it the data, like, does this apply? Is that correct?
Tim:Yes.
Collin:So how could this be applied to someone's existing Ruby objects? Is it something you would wrap? Is it always something you kind of call externally? I was sort of thinking about it in terms of that this isn't like a mix in or something. It's like its own class.
Tim:Yeah. And, and that actually gets at one of the dry-rb principles in that we believe that classes and their objects in your Ruby apps are better if they're focused in their purpose and can be reused in different ways. And composable. So this is why we instantiate a contract. So you can create a contract instance and then use it any number of times, which means you can also inject it as a dependency to some other concern. The boundary is much clearer there. By not mixing it into another object you are keeping the objects that use it, they're purpose clear, and this object itself can be instantiated and even tested directly, which you may not wanna do for all your validations, but I know that the bigger an app gets the gnarlier some of the validation requirements get, and you don't necessarily want to test every niche edge case of your validations by running through a broader purpose test, like an end-to-end test. Instead, what you might wanna do is test your happy path, sure, through the end-to-end, but then have a range of unit tests that get at every nook and cranny of your validation and do so in a way that's tight, that's focused and that can help you test all the scenarios in as little time as possible.
Collin:Right. So this could be, this could go along with like active record validation. It's not necessarily a replacement for it?
Tim:No. If you're thinking about dropping this into a Rails app, I could see a world where you, you know, you still keep some sort of really key foundational validations in your active record models, for instance, but then in your service classes or command classes or whatever you call them, The first thing that they do every time is run their input through a validation schema, and from that point forward in their code, they can operate with confidence on the data that they're going to then pass through to the other parts of the system.
Collin:I think all of that makes sense. What do you think Joel?
Joel:Um, yeah, I was just looking. I don't think I've used dry-validation directly, though. I'm wondering whether I may have used it through using dry initializer at some point. I dunno if dry initializer depends on dry-validation.
Tim:No dry initializer is standalone.
Joel:Okay. Right. Yeah. I was wondering, um, is. These validation classes? Are they, are they callable? Cuz I was just thinking about the fact that you can, I think you can pass a block to an active record validation. Uh, and if that's the case, then you might be able to actually just pass one of these classes as a custom validation in active record. I dunno if that's a pattern that's been used, but I wonder, I wonder if that's a way you could actually integrate it.
Tim:They are horrible. Uh, and that could well be possible. I haven't tried,
Joel:Yeah,
Tim:because my personal preference is to treat the objects in my system that are tasked with persisting data to a database as fairly low level and then
Joel:right.
Tim:build, logic around them in a sort of layer that sort of acts as the interface between that persistence area and my business logic area. So I typically use validation contracts as standalone. I think that's been mostly the use case. Uh, because they are independent, uh, little things, uh, yes, it, it does open interesting, uh, usages that we might not even have thought of when we created the gem in the first place.
Joel:Yeah, this, this is so interesting. So you would say you wouldn't put your validation at the controller level or you, but you would put it between the controller and the model, is that right? So the model doesn't need to be responsible for its own validations, but there would be some layer between those two.
Tim:Yeah, that's, that's how I would think about it.
Joel:Yeah.
Tim:because, you know, there are different, I mean, even think about the simplest kind of blog system, for instance, uh, if you had different levels of editorial control, where you just have, uh, you may have people who are allowed to change the publication date, on a post because they're the ones managing the final workflow versus the ones who are authoring drafts and. Putting them up for approval or things like that. So when you are building the code to manage the inputs to the system, like you'll have different screens for those people who are preparing the, the posts for publication versus people who are adding in the content, ready to be checked and, and vetted later on. So each of those screens would have its own validation schema, and this is why it doesn't make sense to put into the one place that would ultimately persist this to the database because then suddenly that persistence focused object is bound up in your business logic as well. Um, and you, that just gives it more responsibilities, uh, than would make for a, a nice, easy to maintain object that stays small and stable over time. You want the business logic should be separated into the places that are, uh, actually dealing with those flows and the validation schemers can fit really nicely into those areas.
Joel:Right? You don't want to have a ton of conditionals inside your model validations that are just like, okay, but if this person has these credentials, then do this other thing and otherwise do this other thing. If you can actually just model those events and operations, I guess, between a controller and a model, this is a great way to do that. That's, that's, that's really interesting. I, I haven't actually used it directly, but um, I have used that kind of, uh, approach, I guess that, that wider approach.
Tim:Yeah, it's interesting talking about a standalone validation tool suddenly becomes a lens through which we can think about the architecture of applications more generally. Uh, so we started talking about dry-validation, then we talked about standalone validations, and now here we are saying that, yes, when I build Ruby Apps, by the time I'm calling the things that persist data to my database, I want that to be in a completely trusted zone. Those things should not be invoked anywhere else, but, but through my actual business logic layer, after it's done, all the, uh, foreign input, validation, after it's done, the permission checks, after, done everything else, there's nothing else that should reach into those deeply tucked away persistence objects because they become much easier to manage when you know that nothing else is gonna call into them. And you can focus your testing and efforts differently at those different layers.
Joel:Mm-hmm. Yeah, that makes sense. I think you can, you can almost think of it like you want to model the public interface of your application in Ruby, even though that that's not actually a public interface. And then also have the separate private interface in Ruby. And then your controllers, your, your REST api, your GraphQL api, your, uh, your web interface can all talk to that public interface, uh, which are like, some people call them operations, service objects. Um, and that's where you can do like all of that business logic and your validations. And then those things get to talk to the private. Actually, this is something that's persistent in the database. I think that is something that, like, that approach has been used in rails by some people, but it, it's not like very rails native. And I think, um, it's, it's super interesting to see you pushing that approach forward in Hanami and dry-rbi, uh, and with these libraries kind of almost making that available more easily available in rails as well. I think, I think a lot of people have been preaching this is is like good practice to do in rails.
Tim:Yeah. And this, this sort of gets at what led me to be involved in this ecosystem in the first place, uh, after writing. So I, at the time, I was helping to run a client services agency, which meant we would write up, you know, a dozen or more things each year for clients, uh, some of which we would build once and then let them look after, some of which we would look after ourselves for 6, 7, 8 years or more as sort of like, Uh, an arms length development team, uh, for those, for those applications. And what I felt after doing that time and time again is that despite my best intentions, uh, there was still always commercial pressure and I would end up shipping things that I did that didn't quite satisfy me. Uh, and those, those shortcuts that the framework made easy to take in the early days in order to ship, would come back and bite me years later. And, you know, one of the nice things about running a small shop is that we were fairly nimble and we could experiment. So that's when I decided, let's try something else, and that doesn't mean jumping ship from Ruby, which was a popular thing to do for quite a while. Instead, I sort of hunted around and found some like-minded people, uh, with similar motivations and a desire to build another ecosystem to help people who felt like I did at the time. And that's how dry-rb formed. And that's how dry-rb and Hanami came together. And now we are here eight years later with what is beginning to feel like a really compelling, cohesive, ready to go alternative stack for people who love using Ruby, but feel like they would like a different approach to organizing their applications.
Joel:Mm-hmm.
Collin:So if someone was interested in dry-rb and wanted to get started with it, where do you feel like the biggest immediate win to sort of progressively move your code base into this approach would be?
Tim:Uh, it it would be the, the gems that we have a range of gems that can drop into any app. And I think. If we're thinking about rubies as mostly writing web apps, uh, there are a couple of things that people tend to do quite a lot. Validating data is one. dry-validation is, is one of our more popular gems for that reason. Another one that's really useful to use is dry-struct. This has been around a while and Ruby has kind of built some of this into the language now with the data class. Uh, that was released in in the most recent release. But what dry-struct gives you is a typed immutable value object, based on the type definitions you give. And dry-struct under the hood works with another one of our gems called dry types. Uh, that is a type definition gem. So it lets you say things like when you use it with dry-struct, it lets you say things like, hello, I'd like to make a person class. Their name should be a string, their date of birth should be a date. Their bio should be a string and it should be constrained to less than 500 characters, and so on. And each of those things is a single type object. And you can define your shared types in a single place and reuse them across all your structs. For instance, if you have some types that are quite common throughout your application. And then once you have this struct class, you have this person, you can only ever initialize it when all the attributes match their type expectations. So it means, you get very early feedback. If you're given a set of invalid attributes, the object one initialize. And then when you do have that object, you can depend entirely on the contents of those attributes. And because these are value objects they're not mutable, there's no writers for the attributes, you can pass them all around your application and know that. It's safe to do. So there's, there's no sort of link back to the persistence layer or anything like that. It just becomes a very neat way to encapsulate certain entities or the nouns in your system
Collin:So I would imagine this would benefit. Uh, I know in MRI we don't tend to have, you know, like true concurrency, right? Where you're actually doing things at the same time, but in like J Ruby, you can, right? And so it seems like this would really benefit any kind of concurrent thing that you're doing. So I'm familiar with this from other languages where you, you know, you have immutable data types and then you have objects and you tend to use them in the way you said. Does that sound about right?
Tim:Yeah, it certainly helps with concurrency. The other thing it helps with is just Ease of understanding and ease of testing because you take away the dimension of time from these objects that they are, they, they are there and they can't be changed. And it, it means that there's one less thing you have to think about. For instance if you're passing something from one, like key subsystem of your application to another, it makes sense to do so in a way that encapsulates that data, but nothing else from the originating subsystem because you want these things to be hidden in private and STRT classes are a very nice way to do that.
Collin:Yeah, you don't want your data to change out from under you in a lot of circumstances.
Tim:Yeah. Probably the third dry-rb gem that would be useful for someone wanting to play and to do so regardless of your environment or which app you are working within. Be dry-monad. These are our implementation of the common monads in Ruby. But. Without sort of having to bog you down in the mathematical underpinnings of those, like, I make no claim to be an expert in that side. But for instance, if you are, we've talked about service classes before already, uh, what you can use dry Monets for is to give them a common returning faith. So when you call your service class, typically it'll succeed or it'll fail. And how do you do that in Ruby? Do you return Neil on failure? Do you raise an exception on failure? Do you make some sort of like special payload that you signify internally as like, oh, when it's structured like this, it's a failure. Instead you could use something like dry monets, which actually provides a result. A result class which then gives you a builder called success and a builder called failure. And then you just wrap your object in success or failure. And then everything that deals with the results of your service object or operation or so on, uh, then needs to deal with success or failure as a first class concern. It try and actually get at the internal value you've returned without checking, Hey, are you a success? Are you a failure? And it means that the callers of these classes become consistent because they're always wrapped in those same result types. But they also mean that the failure path is treated as just as important. And more importantly, it starts to let you chain these things because with result objects from dry mods, you can put them in a sequence this then that, then that, then that. And as soon as you hit the first failure, it's short circuits, the remainder of the chained operations which can make for some really concise, but also really safe. Change business flow handling.
Joel:Similar to that, you also have a maybe type right, which is, uh, this may be something also may not be something essentially, and in the same way that it makes you handle the failure case. This makes you handle the nil case essentially.
Tim:Yeah, that's right. And, and a few others that are actually quite useful, there's a try Moad, which is similar to success failure except where you are expecting exception to be raised from the code that you're calling. So it takes care of that for you. And there, uh, some others that are a little bit more obscure, but still useful, like a future one, which is kind of like a promise implementation as well.
Collin:Yeah, this seems like something, which I can tell took a lot of inspiration from other languages because everything you're saying is something Swift does actually, which is another language I'm very familiar with. So I think about these as like optional unwrapping is what, where you get a result type back and then you say like, is this a value or not? And then you can do a chain of it, like you said, where you'll say like, do all these things and then you, and uh, in Swift you will say like, if, let you know if this variable equals, and then the failure case would be else. So this is sort of like that.
Tim:Yes, it's exactly like that. We weren't the first people to do it in Ruby. Uh, we took inspiration from a few Murad implementations that came before us, but I think we've built a fairly comprehensive collection and a fairly approachable interface. And as such, it's sort of being picked up a little bit more widely now.
Collin:Ruby seems like an ideal language to be able to add this in as a package because it's so Phlexible and expressive. You can do this and have it almost look like a part of the language, right. Where in some other things it feels like you would have to make this a much heavier thing than dry-rb even does.
Joel:You mentioned a few of the dry gems. The one that I've seen in the wild the most is dry initializer. Everyone seems to be using that, and that is essentially you get this mix in that you can put in your class and you don't have to define an initializer. Instead you can define just using this macro or these two, two macros, peram and, and options. The positional and keyword arguments, and you can give them types at the same time. Right. And this will set up an initializer, but it will also optionally set up, uh, assessor methods as well. And it, and it allows you to specify those tri dry type, um, I'm guessing it's using dry type behind the scenes those type annotations that you mentioned.
Tim:Yeah, dry initializer, it's, it's similar to dry-struct in some ways, but it's a little bit more generic, low level. As a, as a tool. Because it's a module that you extend into a class, it doesn't pretend or have any ambitions to own that class and its life cycle. Uh, instead it just tries to make the job, uh, well dry up the simple job of having an initializer that may accept the range of arguments and then give you a common set of tools for processing them or checking them or getting them into the right shape before they're then assigned to those instance variables. So yeah, it's, it's another example of a small thing that's found its way into many different places because it's is intended to be fairly discreet and let you work with it in whatever way you want.
Joel:Yeah, I, I've seen this in like dozens of different places now. It's all over the place. And it's, yeah, it's really great. I actually removed a feature from one of my libraries because I realized that if people wanted to do this, like my feature just wasn't really tackling all the edge cases and it would be far better to just say, why don't you just use dry initializer with it?
Tim:Nice. that's that's a great outcome.
Joel:Yeah, it's, I mean, yeah, it's just like one less thing that I needed to focus on, and I know that. There is a team of people who are like able to just focus on building this and making this ideal. Um, and it's better, better that I don't try to reinvent the wheel all the time. I love how it's just like this tiny little mix in, you don't have to like subclass anything. You can just mix it into whatever you've got going on.
Collin:So something I noticed was that a lot of dry-rb seems to have to do with types and having the correct types and creating constraints around them. So would you say it's kind of like a runtime check in the same way something like sorbet or RBS is not a runtime check. Like do these go together? They related. Am I imagining this?
Tim:I think, what's a good observation? I think we were all motivated by wanting the ability to create stricter Ruby apps and to consequently give ourselves more confidence in how these apps behave. And yeah, they, they approach the, the problem from a different angle. Toey, they are all runtime. Uh, there's no sort of compile time, anything they sort of evolved in parallel. And, because they can be applied at the places you care about the most you don't necessarily need to change any of your ruby tool chain to adopt them. Uh, and yes, you, you, the, the benefits may not extend as far or as, as, as left in the developer lifecycle as something like survey might do. But they can still give you the level of certainty that your, you are looking for, at least for certain areas of your code base.
Joel:Yeah, you, I think you can get a lot just from managing. Like writers and initializers for your main like objects. I think that gives you a ton of, a ton of Phlexibility and without having to break other things. Or like if you're doing, if you're, if you're not doing it at runtime, you have to track all of the types. You can't go, you can't really go halfway. You have to know exactly what every type is. Because if you, if you like try to pass in the result of calling a method and you don't know what that method returns for sure then, then there's no way you can check it. Whereas if you do it at runtime, then, then that works fine. I've seen this in use. I think there's a pretty simple way to configure it so that it does these runtime type checks in development and in test environments, but it doesn't actually do any of that runtime type checking in production. So it's not even giving you that performance hit in production either.
Collin:So maybe this is a good time now that we have kind of gone through what dry-rb is and all of that. Maybe this is a good time to sort of segue into, uh, Hanmi, which is a full stack web framework and ha's tagline, which maybe you wrote. This also is the web with simplicity. So what, what does that mean? What makes Hanmi simpler than the altern?
Tim:I think it, so firstly, those, those weren't my words. Let's just get the record straight. Uh, Hanami and, and this gets at something else that I think is actually really. A really good story before we delve into the details of Hanami. It's an example of collaboration in the Ruby ecosystem. Uh, the dry-rb organization and all of us gems evolved. They sprung up, you know, on their own. Meanwhile, Hanmi has started a just a year earlier and after a few years of parallel evolution, our two teams realized that we were all aiming in largely the same direction, and that it made sense for us to work together. Like we create a stronger outcome by doing that. And so that's what we've been doing ever since 2018, uh, quite a while now. Uh, but because each of these organizations had their own gems, had their own community, uh, had their own sort of miniature ecosystems, We preserve that, and that is actually really beneficial separation. So Hanami, which I'll talk about in a second, uh, for the version two, it's built on and around many of the dry-rb gems that we just talked about and many that we haven't yet talked about. Uh, but those gems remain standalone gems. So there's a benefit there to anyone who doesn't necessarily want to whole hog adopt a different full stack app framework. They can still use those things. And because that there's that just enough separation, different names, uh, different, slightly different core teams, slightly different user bases, it means that the gems that are meant to be independent, the dryer jam for instance, stay as as maximally useful to people who are not just happening to use the fully integrated app framework, which is a really good outcome, I feel. And at the same time, I think what we've done with Hanami is, is take. All these gems, combine the ones that make sense to be there by default and come up with an experience that's greater than the sum of its parts. Uh, so if anything we've talked about so far today is interesting to you, but you dunno how to plug it into any of your existing apps, well maybe toy around with a new Hanami app and you'll see more readily and just how these things can plug in because the framework sort of guides you in that direction. Uh, but if we wanna talk what Hanami means, uh, the web with simplicity what it means is, is a framework that kind of encourages the things that we're talking about at the beginning when we thought about why the standalone validation library might mean when you follow that thought through to its logical conclusion, it means separating the responsibilities of your application into small, reusable, focused, easy to understand, easy to test units. And that's what simplicity means in Hanami. Just enough of what you need and no more. So if you think about a new Hanami app, uh, this applies to both Hanami one, which has been around since 2014, and Hanami two, which we're in the process of releasing slash finishing right now. Uh, every hey endpoint, uh, in a Hanami app is backed by a single class, we call them actions. So you no longer have a, a grab bag of, uh, related but not identical like related aspects, but all have slightly differing needs, which means your, like large controller style class has to bulk up with additional logic that applies to two methods, but not the rest of the five or so on. You can just create a single class. Uh, it has its job. It can include dependencies from the rest of the application as it needs to call into the app. Uh, and because of that, it also means the responsibility of the action remains focused on H G D P logic only. It's intended that you then go and call into your applications business layers to do the work, uh, get the result, which you could wrap up in a, in a result object if you want, uh, in interrogate that result and then determine the H DP response to send. Uh, so that approach, which we just talked about, being about a single H d P endpoint encapsulated by a class is wri large across hanmi, the framework, and it applies to every aspect.
Collin:Talking about having those separate, you know, each action method is actually its own class. So the idea is more separated out. I noticed one thing is instead of like in rails where you have this just kind of paras object that you would call, right? Uh, that's actually passed to it. And so the more Hanami way of approaching this would be instead of having this class, which is your, just your controller, you might have all these actions and then they would call other small objects or maybe have them, you know, injected. Is that sort of the kind of philosophical difference
Tim:the vibe. Uh, simplicity has many facets, uh, how you separate your concerns as one. But another one, Colin, you just mentioned was statelessness. Hanami actions are classes, uh, and, and, and access and control to the classes. But I'll start with statelessness. Uh, Hanami actions can be initialized once and then called many times, and this means that we need to provide the code that you as the application author needs to write. Uh, we need to provide you the actual objects that you want to work with. So in hanani actions, you have a handle method and it's passed a request and a response object, and you access the request for what you want, and then you put things on the response to prepare, uh, you know, what gets sent back over the wire. And there's no, there's no other, you don't mutate state as part of doing that. You have you, you take an input, you do some work, and you prepare an output. And that's what Hanami actions are oriented, uh, oriented around. And the same thing applies to every other class that we encourage app authors to write in Hanami apps. So the first thing, one of the, the best things about Hanami two is that it provides a mix called depths to specify your dependencies. And you include, include depths into any class. It doesn't matter whether it's like a base ruby class or something that inherits from a Hanami class like action. And then you provide a, a list of identifiers for the dependencies that you want to bring in from the rest of your app. And those identifiers that are strings, they look like, uh, a, a match for the class names that you, that you're writing. And then they become available to you as methods in your class. And what that's doing behind the scenes is writing an initialized method for you that receives those arguments as injected dependencies, but it takes care of all the boiler plate. You just write a single line. So this means in the end that every single component in a Hanami app you can call new on it. And this means that you can start to unit test it in the way you want. You might not necessarily want to unit test everything. Because those tests aren't always, uh, valuable. But for the parts of your apps that you do, you can call new, you can pro provide substitute dependencies if you want to simulate or mock out behavior, uh, that is heavyweight or that you want to simulate for the, the sake of the, the, the test. And then have a really tightly focused unit test. Uh, and the fact that every object in a Hanami app can be nude is a really powerful concept. It's actually, it's, it's actually kind of shocking that this is something that is like a novel thing for many rubyist that they can call new in a class and do stuff with it. Uh, but that's the goal of the framework. It's not to, it's, it's not to hide too much of its. Sort of connective tissue away and, and do so in a way that makes it inaccessible. It's the inverse. It is meant to let that basic ruby be accessible to you as the application author and, and in doing that to help you understand how things tie together and to give you the op opportunity to access those lower level things when you need, or even swap them out if you ever need as well.
Joel:So how, how, often is the framework calling new on these actions? Is it true that every, every time that the action is used, it's a new instance of that action? Or is the same instance used multiple times for different requests?
Tim:Let's just go back a step for me to explain this. So in, in Hanami you have an app object. Hanami app. Uh, it's, it's, it's the instance that you, what you create. One instance of this, it's in config slash app that should all feel pretty familiar, but then to access all the components of your system, things like your service glasses and so on, uh, you can get them off the app using the square brackets syntax, just like you're sort of getting something off a magical hash. And then it returns you those instances and all of the dependencies of those instances are also resolved and then made available to you and so on. And when you do that, yes, it's calling new on it a fresh each time. However, that is an internal concern, and it's something that we're actually thinking about changing in a future version as an optimization. Like we don't need the object churn necessarily when these things are intended to. Stateless and can be called multiple times over with no ill effects. So for now, that's how it works. Uh, it is something that you can control, a kind of an advanced component registration feature. But, yeah, at the framework level, we'll be able to tweak the defaults a bit to optimize the runtime of deployed applications.
Collin:Would you say another way of saying a lot of this is. A difference with say, Hanmi and Rails, is that Hanmi seems a lot more explicit and maybe less magical. Would you? Is that a fair classification?
Tim:I'd say the level of magic is lower, where we, we choose to concentrate the magic in a couple of small places and make that accessible. Once, you know how included depths works as a way to bring together components from your app into a single place to, to allow you to create higher level behavior. That's then, you know, the, the magic of Hanmi. So yes, there is more explicitness in that you do declare the way things connect. And I think that's actually really powerful because it immediately upon authoring a new class, you think about dependencies as like the thing that goes right at the top of that class definition as opposed to those dependencies being sort of implicitly sprinkled throughout the whole thing by concrete class name references, you know, and so on. And when I wanna bring in a dependency, do I make a private method that then memorizes that dependency or do I do something else instead? We have a single conventional way it's used consistently throughout the framework and. You can use it for every aspect, whether it's an action calling into the business layer or whether it's the business layer calling into the repository layer and and so on. Uh, that, that one trick, once you've learned it, it's there for you everywhere. So your apps become more explicit in that you do have this sort of manifest of dependencies at the top of each file. Uh, but that's actually like, there's a huge amount of value that you derive from typing those things. It's not boiler plate for the sake of it. It's actually telling you something, Hey, I'm a class. I depend on these three things. And if, if you wanna know more about those things, you don't need to see it. Just trust that they work, which is that great for peace of mind when you're authoring a single thing. You can just focus on the task at. But also, you can go delve deeper if you need, but if those three things become 30 things, that's a sign, right? That's saying, wow, maybe this class is doing too much. Uh, should I actually break it down into a collection of smaller things that each one has a smaller set of dependencies, like it gives you that signal about the shape of your app, how one thing is connecting to another. It should help you create clearer boundaries between the different layers in your app. And those boundaries are actually really important, uh, for the long term maintainability we talked about business logic and persistence logic before. That's another really defining feature of Hanami. So just to be clear, we've released 2.0, uh, but only half of the full stack experience. We have the view layer and our persistence layer coming in the next few months, but the way we design our persistence layer is so that it is separate. You have repository classes. That provide the interface to your database persistence. And those are injected just like everything else. So it means all the lower level details of database persistence you don't need to reach to. In fact, you shouldn't. The repository serves as the interface and that is an interface that you control. And so you then know what in, what, what includes the repository as a dependency because you've written it and you know what methods it's called cuz you've written those methods. And the the simple things remain fairly simple. Crud remains easy, but the complex things remain possible and possible to do so in a way that doesn't require you to eject yourself from the standard approach. You can just add it alongside those simpler things and they all still fit.
Joel:I think, I think the term magical is massively overloaded in Ruby. Um, probably by rails a lot. And it can mean a bunch of different things. Like magical could mean sparks joy, right? Or it could mean very implicit kind of like lots of macros stuff going on behind the scenes. I don't understand. That just seems to make things work, even though I don't understand how it works. Like people use that word to describe all sorts of different things, and I think that. To me when, when I use Hanami, it's like I'm getting the sparks joy kind of magical, but I'm, I'm not getting so much of the, like, I don't understand why this is happening like this, or like my class is doing, like handling all of these concerns for me behind the scenes. But that, like you said, means that I don't even notice that necessarily and realize that I could be breaking this up into a smaller concern.
Tim:Yeah, I think like a feeling I get when I work on Hanami style apps is that I can see the handholds. Which, which I, I'm scaling a wall as a, as an app developer, and up the top of the wall is the feature I'm trying to write, and I can see where I can put my hands and my feet as I go up that wall because the app is broken into small pieces. And while I can add a new one, or if I need to change one thing while I know exactly where I can slot it, and I know what the flow and effects will be, because I know in turn what brings this thing in as a dependency. And that's a really reassuring feeling for me as an application author. Whereas the alternative, you know, if you have a model class in a, in a Rails app, for instance, uh, it's used everywhere and anywhere. And so it feels, and the interface is sleek and smooth, and that's, I, I'm not knocking that that can be really empowering for a certain type of developer, just not for me personally, because I don't know where to plug in the thing because I'm not sure what the flow and effects will be. Uh, so I, as a, as a individual rist, uh, prefer, uh, the Hanami approach, which is why I've helped build it for myself and all the other people out there. And these two things can coexist. These two things can provide opportunities to learn from each other, and I think that makes for a stronger ruby ecosystem in general.
Joel:Definitely, definitely. Yeah. I think I'm like somewhere in the middle of that personally, which is difficult cuz no one's building a framework for the middle.
Collin:Reminds me a little bit of Phlex in the way that everything is very componentized and broken down. Which Phlex I feel like also makes very easy.
Joel:Yeah. I think there is some similarities. Phlex is very, uh, very much okay with mutating state as as we go. Um, it, you are not gonna initialize a single Phlex component and then reuse that multiple times with different arguments. Every time you render, you're getting a, a fresh Phlex component, and it's mutating state as it's being built. And so that's quite different. One of the things though, that I would, I, I think, I think just since we're talking about Phlex, there is an example of a feature in Phlex that I think demonstrates this like difference between two types of magic, and that is the way that Phlex implements slots. Compared with view component. So in view component you have, uh, a macro to define a slot. You can say renders many or renders one, and then you give it a symbol as the slot name and that actually defines for you a few methods. So it defines a method on the component that's yielded that, that is. So if you say like renders many, uh, I don't know, list items, right? Then you will have a method to be able to add list items to the component when it's yielded. And then you'll have a list items method to be able to read them. In Phlex, we don't have anything like that. We just let you define that method yourself. And because of the way the argument forwarding works, you can do it in like two lines of code anyway. And I feel like that feels just as much like magical in the sense of sparking joy, but it doesn't have anything like the complexity going on in terms of, um, inflecting plurals and, and singular words and all that stuff going on behind the scenes. And also just not knowing what is this line of code do, does it, what methods does it define? Um, so I think in some ways I'm striving for that same kind of simplicity, but also not trying to like do the, the kind of dependency injection and stable objects, uh, things like that. I'm not sure. I don't, I don't really, I don't really know, um, very much about building software. So I'm just kind of making this up and doing what feels, what feels good.
Collin:All vibes.
Joel:yeah. You've been working on optimizing performance of Hanami view and it sounds like you've been doing some really interesting things in terms of doing that. Like you built a new er b engine. I've super interested in digging into that.
Tim:Hanami view is kind of an interesting part of the puzzle. Uh, it does a lot more than Phlex, for instance, because like for starters, it follows the same pattern that we apply for many other parts, uh, of Hanami, which is. Uh, views can be initialized, and then you can call them with arguments, uh, and you get back the rendered result. Uh, so a view can become a dependency of any other part of your system, and that's a really powerful, uh, arrangement in Hanami apps because in your actions, you then, uh, can include your view as a dependency, and then you can render that view to return html, um, to the browser. Uh, and in fact, that is one, you know, we talk about reducing unhelpful boiler plate in Hanami. When we have a view layer shipped, the matching view for each action will automatically be included for you. You just type view inside your handle method and the view is there, uh, but you can override it. So that's like, that is a nice, um, resulting effect. Like if you want the defaults, it's there, but you can also replace a view with, with whatever you want to. So having views as instances that can be called many times means that they can become those dependencies of other parts of the system. It's also really valuable, uh, in that the view, the view library, uh, PAMI view, uh, used to be called Drive View was one of the things that we actually shifted, uh, as we thought about the best boundaries between our two projects. Uh, it can be used on its own, uh, insider Hanami app as well as, uh, outside Hanami apps in whatever other Ruby project you're working on. So I've actually used views, uh, as objects that help me deliver emails. So when you are working with a third party, uh, email delivery system, and you need to provide them a HTML template for the end of text template, for the contents of that email, We already have a thing that knows how to receive arguments, interpolate them into some template, and then pump out a result. That's our views. And because views, standalone objects, well, what I did then was create a new namespace in my app called emails, uh, or, or what have you, and created, uh, a view class for each of the email types I wanted to generate. And hey, Presto, I've been able to reuse a part of the framework in a way that, you know, maybe wasn't the intention when it was written, but is perfectly well suited to doing that job. And in fact, we had a base class that added some sort of extra email, like, uh, APIs to it. And where we go now, we have an email delivery mechanism that can be injected into every part of my app and it just lets me write views like everything else. Uh, totally conventional. So there's some really nice flow and effects there, but it does mean views have a little bit of extra work to do. Uh, so we provide a, uh, an exposed class level method. That lets you prepare the data that you want to pass to your view template. So, uh, if you pass in, say like a post slug, the building, a blog site, again, you pass in the slug to the post. Uh, and then it can be the job of the view class to, uh, include a repository as a dependency, get the full post object and then pass that in. So views can become quite independent and easy to call because of that. Uh, but yeah, it means that yeah, there's a lot, a bunch of extra work they do upfront, uh, when you call them to get that data into the right shape and then pass it into the template. Uh, the, the reason, and yeah, there was some inefficiencies because we wrote this, we wrote this thing to make sure it was functional. Uh, it's been in production for six, seven years in the form of drive view. It's been doing its job. But one of the things that we've been very focused on as we prepare for the 2.0 releases of Hanami is making sure that we are. Staying true to the claims that we're making, that we're, uh, a lightweight, fast framework. So, uh, iterations per second and memory usage are things that we've been revisiting and making sure that we are comfortable with our results before we release them. So that's what led to some performance tuning. The e r b thing is interesting because one of, one of the things that's nice about writing stuff that's new is that you don't have to carry forward any baggage. So one, one of the outcomes that was always important to me about, uh, Hanami view is that when you are writing your own methods that get called in the view, uh, and you can do these in a number of places, this is one of the interesting things about it, is that every exposure that you declare in your view, uh, and that gets passed to your template, is wrapped in a part class. We call them view parts. Uh, and it's just a decorated class, but it's baked right into the view layer, so it's done automatically for you. And if you define your own name, space of parts, like you make your own, you know, um, my app, colon parts, colon post class, if you expose a post, uh, then it'll find that class automatically and wrap it for you. So you get a consistent view object decoration system built into the view layer. And you can choose to use it or you can choose not to. That's one of the things that we've tried to do here, which is we provide extra levels of power facilities and so on, but you don't need to worry about them unless you feel like you need them. So in these, in these parts, you get the value provided. In this case it'll be the, the post struct, like it has all the attributes of the post, but then you can add methods that are used specific. Uh, and that makes sense for you to make available. Anytime you expose a post to any part of your view layer, these things will be there for you. But that don't necessarily make sense in the, actually the, the base post class because their view concerns and they're not, you know, um, core business logic concerns. Now, getting back to the EER B thing, what I want is that anytime I call yield in one of these methods, then I like the block that is given to that should just be the string that comes back from it. Um, if it's like yielded inside the template, the, the nested template code inside that block should be captured implicitly and then returned when you're dealing with the return value of that yield. Uh, now in every common ER b implementation, that is actually not a thing that happens. Uh, you have to use like a capture helper. Uh, and it sort of does things like it knows about a string buffer instance variable and all these like complex things that me as like a Ruby person just trying to write an app shouldn't have to worry about like, yield is yield, it should just work. And things like Hamel and slim, uh, when you're using them outside of rails do this for you automatically. Uh, you don't have to worry about it. But we've never had an er b implementation that does that. And so this is what we built inside of Hanami view. Uh, it's, it's not, it's, it's taking advantage of a lot of things that exist already. I'm not trying to claim I've done something radically new, but I've just made a slightly different arrangement. So it uses Temple, which is a fantastic template engine library, which both hamel and slim use. Uh, and that's, it just sort of takes temple's reference er b implementation and introduces this concept, uh, and at the same time retaining all the other things that are important. From the common e b implementations, like auto escaping of, uh, values as you pass them into the template so that we ensure we're protecting our users from common, uh, attack surfaces and things like that. So yeah, that's, that's the story behind, uh, the e r b engine.
Joel:Does that mean it's able to be compiled? That is that the ER B is compiled? Cuz I know that there are some issues with doing that in rails.
Tim:Uh, well, the e r B is compiled by Temple, uh, so it will be turned into a Ruby method, if that's what you're asking.
Joel:Yeah, that's what I
Tim:when. Yes. Yep. So that's what Temple does. And then our view system is built on top of Tilt, which is another amazing longstanding Ruby library that provides a common interface to rendering across a range of engine. So we provide first class support, uh, like we actually test for Hamel Swim and, and now our internal e b engine, which is, is vendor, like it comes built in. Uh, but you can also use any other tilt supported template. So I've actually used Hanami view to write API responses using Yael, Y A J L. Uh, so you can write templates that just prepare adjacent, like a Jason variable, and they become the output of the template. Uh, so it's, you can use, yeah, anything that tilt supports and there's like 30, 40, 50 things out there. Um, and Temple Tilt means that these templates are compiled down into a Ruby method the first time. And then that Ruby method is used for all subsequent invocations.
Joel:Mm-hmm. So one, one of the things that. Rails has in the view layer that I have a real big problem with is the, this kind of like global view context that is actually, essentially come straight from the controller. Um, and that is every instance variable that you set in a controller is accessible in a view and then in every partial that is rendered by that view. I'm guessing that is not the case in Hanami that doesn't seem to be
Tim:Yeah. Like it's, it's not a thing that exists. The exposures are the only thing that go to your template. Uh, and you've been intentional in in preparing those because you've written, exposed some name and, uh, away you go.
Joel:right. So you have an interface that creates a view context. That is, and that is what's passed down
Tim:Yes. There, There, is another piece to that puzzle, and you don't need to know about it unless you feel the need. But we do have a class called Hanami View context. Uh, and that is, uh, you can pass this in. It's a special, uh, part of the, the call method for views. You can pass any exposures as well as any, uh, arguments you want as well as a context. And the, the context is available inside, inside all parts of your view layer. And so, because there is a need for that in some cases, like some stuff that you want available everywhere. Uh, but the idea of turning it into a actual first class concept is that, uh, you, we encourage the users to think about it. Uh, here's a place you can use and here is expressly stated purpose, use of care. Uh, but what we do in Hanami is that we put things that are only available from the current web request in that context because there's no other way to sort of. Nicely pass that in. So things like csrf tokens and things like that, which are request specific go into the context. Uh, the request object itself is available there as well. Um, like the session, those sorts of things. Uh, but the nice thing about it is that they're on a class. They, they have their, the methods expressly declared on the class. They have their documentation. You can go look up Hanami v context in our API docs and see what's there. And when we generate a new Hanami app, we actually generate, uh, actually I need to fact check that, but I think we generate a context class for you. Uh, so, you know, this concept exists by default. It's an empty class, but the purpose of the class is written above it. And you can add your own methods if you feel like you need to add something to every part of the view layer. Uh, the other reason the context is important is because the context is also available inside those part classes. Uh, because they're separate from the template rendering, but we wanted the part classes to have every bit of power and every bit of access to every facility that the templates would, because otherwise they're, they're sort of muted. They're not as valuable. So inside a part class, which is a, like a ruby class, normal ruby class, you can call render and you render a partial, uh, you know, because we've wired up all the same underpinnings to both the template rendering, uh, evaluation as well as the part. And that means that you can really start getting serious about making your templates as clean as possible, because you have these places where you can extract stuff, put it behind a method, test it if you need, and so on.
Joel:Right. I, I think a practical example of that context would be something like, uh, a partial that wants to check whether or not a, a link is active. Like if you're on, if, if a, a particular link that you're trying to link to is the current page. I think that's the, that's the one that I reach for the most when I'm building views is like in terms of having a, a global context. But if you have that global context that is, is stable and is available everywhere, that's one thing. What, what I struggle with with Rails is you have this kind of implicit context that comes from the controller. You can extract it into a partial, and then you use that partially from a different controller and you're like, why doesn't this work? Well, it's because that controller doesn't set that instance variable that your partial was depending on, and you had no way of possibly seeing that without just reading every line of the partial,
Tim:This is the flow and benefit of the view in Hanami being yet another thing that is a standalone class that you can call. Because you need to think about the interface into that because it's no longer implicitly part of some larger hole. Like it is a separate thing. Uh, and it needs to be something that can be directly used outside of like any context. Like it's not just used within, uh, web requests and so on. I'm really proud of this part of Hanami two and it's not something that everyone thinks about, but all of this, everything we've talked about with views and actions and so on with Hanami two, these dependency, these gems, Hanami view, Hanami Hanmi controller and so on, they're actually in your apps GEM file. And that's because we've written the framework now so that it's no longer just a web framework. Uh, it's a framework that can serve any kind of Ruby app. So if you don't want to write a web app, but you want something to provide conventions around your code structure to provide environments like test development production, to provide settings, loading from end, to provide ways to initialize special components, um, from those settings and so on, uh, they're all really valuable things to have in Ruby Apps at in general, those are not web specific concerns. So you can generate a new Hanami two app today and rip Hanami router, Hanami controller, eventually Hanami View out of your GEM file. And then you can go write, uh, a chatbot. You can go write a stream consumer, you can go write a CLI tool, uh, and have all the same, uh, application structure benefits that we talked about in terms of. Having a framework that encourages you to separate your concerns and, and all of that. And having a framework that gives you like a bin console that just works out of the box and so on, uh, without any web baggage if you don't want it. And that I think, is something that is possibly truly groundbreaking in that we have something that spans the gamut of all application types and does so in a way without compromise. Uh, I'm really excited for that, uh, because I've, I've worked in places where, uh, you know, Kafka is a key component of the ecosystem and we wanna write Ruby apps because we had some big rails apps. So let's keep writing Ruby, but then we have to come up with our own from scratch way of structuring these things. And honestly, that's not something. Uh, you know, application developers should have to think about, there should be a thing that gives you a conventional, powerful ergonomic app structure, uh, and then lets you focus on the rest. And we get that now with an Army two, and that is really exciting, uh, for me.
Joel:So you've done that by making it completely modular. How do you go about developing a system like that? How do you go about testing a system like that? I guess each of these things are somehow completely developed in isolation and tested in isolation, but then they're also brought together with some kind of like integration testing somehow. Do you, do you have any special tooling to like link multiple GitHub repositories together and like trigger, like you change, I dunno, you change something in Hanami view and so some other repository starts running CI to test Hanami view also works with the rest of Hanami. How, how do you approach that?
Tim:Uh, What you've des described would be great, and if you'd like to come help make it, Joel. Uh, we, we love it. Uh, no. Right now it's just a lot of, uh, careful attention. Uh, but there are some principles that I think help make this practical. Uh, the first one is anything that can go into the independent gems should go, like, we don't hire, like, we don't reserve certain special powers just for when you're using them inside the Hanami app. Um, in fact, like anything that can be useful for the GEM standalone should go there has two benefits. It means all our tests for those things can stay inside the gem itself isolated, and it means anyone who wants to use these gems outside of Hanami in whatever context gets the best possible version of these things. Uh, the second approach we have is that inside the Hanami Gem itself, We do have a range of extensions to these gems that we activate. Basically the glue, like how do we make a actions aware of views and, and wire up some, like the default association to a view and so on, and they live inside the Hanami gem. Uh, and that's where we have all of our integration testing, you know, sort of for the sort of full stack experience
Joel:Right,
Tim:we need it. So, yeah.
Collin:That's amazing what you're saying about being able to apply hanmi potentially to like a CLI application or something. Something that immediately came to mind for me was, I don't know if you've seen like the glimmer DSL for lii where you can build Native Cross-platform UIs and Ruby. I'd be really interested to see if there's a way to like tie that together in here. That'd be
Tim:Mm.
Collin:neat.
Tim:it's
Joel:the native macapp in Hanmi.
Collin:I mean, if anybody would want to, it'd be me.
Tim:There's actually, there are something I keep in mind when I build apps like this, which is, it's a really useful guiding device for structuring the code. It is like, imagine I have a second interface to my app, so I'm building a web app, but also a sweet CLI for my developer friends. You know, uh, am I structuring my code such that anything important is stuck in that web layer? Because if so, my sweet CLI is not gonna work anymore. So that encourages me to push things down into a separate layer and have that structured in a way where the key behaviors of my app are independently addressable, uh, by an arbitrary number of interfaces on top. And that even if that second interface is never made, uh, that, that. That train of thought is really valuable for, uh, coming up the most maintainable and, and easiest to understand separation of concerns.
Collin:That's. Amazing. Uh, because having done things where you are sort of writing a lot of code that is then going to have a lot of different representations, that's a very difficult balance to hit where the code may, the code remains simple and sort of readable. And it seems like you've really done a lot of work in that.
Tim:Yeah. The, the whole idea is that, that we want people to recognize that the framework is not the same thing as their app. Uh, the framework should work in service of your app, not the other way around. Uh, so it's, it's meant to be, it's why it's a collection of libraries for starters. Like if you don't like, um, if you don't like one of the things, just go use something else and there'll be a way to plug it in. Uh, but it also means that you think about your app should just be a Ruby app. It shouldn't be a Hanami app. And yes, there'll be certain parts of Hanami that we, that you slot in. Uh, for instance, the thing that the, the depths mix in that you can use to wire up or your dependencies, yes, you, you'll probably get that across your, your business layer. But that is something that you could in theory remove all it's doing is creating initializers for you. Uh, so if you ever wanted to eject yourself entirely, um, you can end up with a Ruby app using a bunch of libraries, but build some other web interface on top of it, and that should work too. Uh, and I think that separation is really enlightening. Uh, and it's certainly the first time I started working with these independent gems and building them with, with dry ib. It changed the way I thought about structuring my application code. Uh, because you're no longer as, you're no longer part of this, it's integrated, but it's. It's in this, this integrated hole, but it's integrated in a way where the connection points are clear and there's not some sort of big goy like morass, if you know what I mean. Uh, and that helped me find better structure in my app code. And this is why I think even if, even if you don't think you'll ever for serious make a Hanami app in your place of work or so on, it can be a really powerful experience for you as a rubyist just to start playing with these kinds of things because it can bring about a mindset shift that can then help you better structure the code, uh, in, in whatever else that you use for your day to day.
Joel:Mm-hmm. How does the Hanami team plan features and stuff? Like, it's a huge project and you've been working on it for a really long time and there's a bunch of you.
Tim:No, it is,
Joel:it's really complex, right? Cuz you've got so many, you've got so many different bits that are somehow meant to be working together and they do, they work together in a way that's like, It's like, it's like choreographed, um, it's like, it's like a, a bunch of dancers like dancing perfectly in, in synchronicity with each other. And, yeah, that must be really hard to pull off because it's not one big monolith really, if you think about it as a framework, it's like, it's like just a collection of little modules. It's like Legos.
Tim:Yeah. It's the, the first aspect of how we work together and, and figure all this stuff out that is important to realize is that I'm in Australia and Peter and Luca are in, um, Roman Crackow. Uh, it's not the nicest time zone for overlap. Uh, I mean, Joel, you're only an hour behind them, I think, or two and I know it's getting quite late in your day. And so we only have very small overlap and we are all doing this as our side effort. Uh, none of us have a work scenario. I did initially have a work scenario where this overlapped cause I was running my own business. I don't anymore. Uh, but this is important to me, so I keep going with it. Uh, it's important to all of us in that way, so we just do the best we can with the little time and collaboration overlap we have. And I think the fact that we've, getting to the results that we're getting to is a huge testament to just having found people who share a pretty good, uh, overlap of philosophy. Um, and the bits where we don't overlap are actually really interesting because I think they're the bits that have propelled her army to another level. Uh, you know, I for instance, came into this with a high tolerance of sort of boiler plate. Um, whereas I know that Luca brings like a really strong vision about as much as possible being tucked up into the framework and the, the user code being streamlined as possible, one at the same time, allowing for all the clear connections that we've described before. And, and that difference, uh, I've learned a lot from working with him in that way, and we've created a really good outcome because of it. Um, but in terms of how we've got it all done, uh, it's just been, uh, a lot of hard yakka, uh, across many years and and we've just had to y yeah, I thought I'd bring that in. Yeah. Hard work, uh, uh, and just being patient and being persistent and bloody minded, I suppose about making sure we get to the goal and, uh, it's also. It's also been the driving force between choosing to ship half the framework initially, uh, the router controller and the core application, uh, to begin with. Because yeah, the reality would be we might have lost steam if we just tried to wait and snap everything off all at once. And I think that's been a good decision. Uh, you know, I was there when Colin, uh, I watched Colin's stream when he tried out Hanami for the first time. He wouldn't have done that if we hadn't shipped 2.0 because people need to see the, oh, the announcement and all of that kind of stuff to make, to make it a clear signal that this thing is ready for you to use. And that, that gave us a boost of energy. It helped us sort of narrow our focus as well. But yeah, it's just about, I think this is also, this is about executing on a vision that we all have had in our heads for 5, 6, 7 years. Like, we knew the pieces, we knew how they had to be choreographed, like you said, Joel. It's just getting it so that it's done. And so that user experience is as refined as possible, is just a lot of work. Um, Hanami and all its constituent components, the triple oh series have been in production for five years or more, uh, because of, you know, uh, places where I've introduced it, uh, as a sort of alpha software. But knowing that I'm there to support it and knowing that it's actually, you know, quite fine. It's doing everything it needs to do. Uh, it just, it just goes to show the amount of effort it takes to produce the streamlined end product. And as a team of basically three people, uh, half of which have life stuff going on at any point. And, you know, uh, aren't always, You know, consistently contributing. It just means that it takes time, uh, and we need to keep, you know, uh, reassuring each other and, uh, motivating each other to, to reengage and, and keep the thing moving. And we're a mere number of months away from having the 2.0 vision complete. So that's really exciting.
Joel:That's.
Collin:I, yeah, I will have to redo that stream because I think that was just about the day that I realized I had R S V, which ended up being the worst cold of my life that lasted like six weeks. So I was a little loopy. Uh, so I'm definitely gonna do that again when 2.1 comes out. Uh, well, I think we've covered so much. Um, and we, maybe we should just have you back because it seems like we could go on forever, but for the time being Tim, where can people find you?
Tim:Uh, hit me up on ruby.social. Uh, I'm Tim Riley. There. Uh, I also blog@timriley.info. I was doing it monthly. So if you wanna know about the month by month journey towards an Army two, you've got a couple of years of developer diaries to read there. I kind of dropped off in the last six months, uh, cause we were releasing, I had, I had a couple of conference talks to write, uh, and so on. Uh, but all my, all my journaling and, uh, conference talks and videos and things are there. There's a couple about Hanami two. So if you want, uh, some more australianism. Go see the most recent one from Ruby Conf a you, uh, I, I, I, I dived into the hyperlocal content to kick that one off. Um, yeah, that's, that's it for me. And just keep an eye up. Hanmi has a account on Ruby Social, so does dry rv. Uh, they'd be a great thing to follow. Uh, there'll be more releases coming up, and we're excited about what we have to share.
Collin:Well, I personally really want to go to Ruby Confin Australia one day. That's a very long flight, but if I do, I'm going to have to make you my official translator then, since I don't know any of these terms.
Tim:Uh, I'll also share another little nugget. Uh, I, I'm going to be at Brighton, Ruby in, at the end of June. So if anyone is based in that part of the world and wants to talk about any of the stuff that we've talked about today, uh, you'll be able to find me there and I'd be keen to say hello and, and meet up with as many people as possible.
Collin:Amazing. Thanks everybody for listening to the show. If you enjoyed it, please subscribe. Tell your friends, hit the star and overcast, or just keep listening. We appreciate all of those things. Uh, I also want to mention. Since this will be coming out at about the same time that I will be at Rails Conf in Atlanta. So if you see me, please say hi or message me on Mastodon. I'm thinking of maybe getting together a small group of people to go to Waffle House or something. Uh, and yeah, that's the show. Thanks everybody very much.