Not really a blog, just some stuff that a future me might need to remember one day.
Rails is such a dominant framework that it has almost become synonymous for Ruby itself. It’s rare to find a Rubyist who isn’t also a Rails developer.
Rails is great but can we all have too much of a good thing?
Is there space for something different, something new? If there is then I think Hanami fits the space between Rails and Sinatra.
Note that Hanami was known as Lotus until a couple of weeks back.
Rails has been a dominant framework in the Ruby community for 10+ years now. There is a good reason for that, it’s a great framework, it has been a source of great ideas and is well executed. I expect to be using Rails for some time to come in because it is such a great fit for a lot of the sites and applications my employer builds.
Having said all that it’s not healthy for all of us to use a single framework and rely on the choices that the Rails team make for us. They are good choices for most purposes but Rails does not have all the ideas, it isn’t perfect and a single framework can’t accommodate all the really great ideas that a community as big as Ruby generates.
If we just want to build something lightweight that doesn’t bring in all the dependencies that a fairly large framework like Rails does we have Sinatra. But what if we want a more complete framework that gets us up and running quickly but doesn’t force us down the Rails way? Hanami plugs that gap very nicely, it also happens to bring along some fresh ideas and it seems to be very carefully thought out.
Ruby has a vibrant developer community that has drawn a lot of smart (and good) people. But as Ruby continues to mature an increasing number of alternative platforms emerge with the potential to attract the kinds of people that make Ruby so strong. Competition, renewal, exchange of ideas are all critical to maintaining a healthy modern developer community.
I’ve seen quite a few Rails applications that have become pretty complex. That’s what happens when applications mature and we add more features, you could argue that it’s a consequence of success. So we need to accept complexity and deal with it.
In doing so we need to strive to avoid some of these pitfalls:
Rails encourages us to put build our applications as monoliths, lots of controllers, lots of models, lots of views all bound together by one application and one framework.
Rails makes it really easy to get started and add features using a set of building block, models, controllers and views. Also helpers, now channels, etc.
It’s very easy to dump your business logic into these building blocks and to couple them to the framework. For example, we dump business logic into our models in callbacks and validations etc. and from that point on it’s tied to the persistence framework. We can’t test those bits in isolation and we can’t easily move them to a different place.
In three words - separation of concerns.
Hanami sets out to achieve long-term maintainability. It eschews magic for more explicit code (though it uses conventions where they make obvious sense).
Hanami sits between Rails and Sinatra in terms of features and rapid development. It is a ‘batteries-included’ framework, in that it provides all the core features that you need to get a Web application up and running but it is significantly simpler than Rails, partly because it is much younger and not yet feature complete. But there is a bigger philosophical difference, it trades super rapid development for a guiding hand that leads to cleaner code.
Controllers are just Ruby modules that follow a naming convention (they
are nested in AppName::Controllers
). Actions are separate classes
nested within their respective controller module.
Here is a simple example from the guides:
module Web::Controllers::Dashboard
class Index
include Web::Action
def call(params)
self.body = 'OK'
end
end
end
Note there is no base class for actions or controllers. The only
requirement is that an action class mixes in Web::Action
and
implements a call
method.
So is this better?
Separate action classes mean that the code in that class has a single responsibility. It avoids the confusion that happens when our Rails controllers start to get cluttered with filters and helper methods and the confusion that leads to when we wonder which action uses which filter/method. It’s much easier to answer the question ‘What am I going to break if I change this?’ when you have single action classes.
And it’s still fine to factor out common functionality from multiple actions. It maybe a little more effort but we just end up with something where the intentions are a lot clearer.
In Rails a view is a template. In Hanami they are separate things. A view is just a Ruby class, another example from the guides:
module Web::Views::Dashboard
class Index
include Web::View
end
end
Views only need follow the naming convention
AppName::Views::ControllerName::ActionName
and mixin the Web::View
module.
Templates are separate files and should be pretty familiar to Rails
developers. The above example maps to a file with name
dashboard/index.<format>.<view-engine>
, e.g.
dashboard/index.html.erb
. (A single view can render to many formats,
e.g. JSON and HTML) and dozens of view engines are supported other than
ERB.
If you want to pass data from the controller to the View you need to
expose
it. Hanami doesn’t automatically make all your instance
variables available to the view so it’s less tempting to start dumping
application logic into templates.
Templates have access to the methods of a view, you are encouraged to put all view logic in the view class, where it can be easily unit-tested without needing to render the view.
module Web::Views::Dashboard
class Index
include Web::View
def formatted_name
end
end
end
Helpers are just Ruby modules that you can mix into a single view or all views for an application. There is no automatic mixing in.
In Rails the model logic is tied to the persistence layer, your model objects can’t be instantiated without a connection to the database partly because of automatic data mapping but also because the same objects contain domain logic and persistence details.
That’s not good if you want to use your domain model classes without worrying about persistence, e.g. in a controller test.
Hanami keeps a clean separation between the domain model (entities) and the persistence layer (repositories). It also makes the mapping between entities and the data store more explicit. There is a clear trade-off here, we have to write a bit more code to make the mapping explicit to get these benefits.
Even it’s authors would not have us drop Rails in favour of Hanami at this point.
It is worthwhile taking a look at Hanami trying to build something non-critical with it and seeing what you can learn. It’s well documented and it’s under active development.
You are probably not going to get the opportunity to rewrite that 5 year old legacy application just because we all fancy trying something new. But you can learn a lot by looking at some fresh ideas from a different angle. To me that’s what is great about having an active Ruby community that questions and explores continually. If you can learn something from Hanami and apply it to you next Rails project then maybe your next 5 year old legacy application will be a bit more fun to work on.