As of today (06/12/2022), Hanami 2.0.1 has been released. Read more about the enhancements, bug fixes and gems in release 2.0.1.
Hanami 2 was released on 22 November, concluding four years of work on this version. It brings a breath of fresh air into Ruby's web development community. Version 2.0 is not just an incremental upgrade. One could say it's a project written anew, with bright ideas from version one rebuilt on top of a solid dry-rb libraries ecosystem.
Since there is no clear upgrade path from version 1.3, in this article, we will focus on some key aspects of the release rather than comparing the two versions.
We will take a close look at the following:
- Dependency management
Let's get started!
Slices in Hanami 2
Hanami's go-to solution for setting boundaries between different contexts of a growing application is slices. By providing separate namespaces, slices clarify which code belongs to which area and when the boundaries are crossed.
Note that, as useful as they are, slices are also completely optional. If your app is simple or you don't yet know what
slices will emerge, you can just put your code under a "main" slice in the
A new slice can be created within an existing Hanami application using a CLI generator:
> hanami generate slice accounts Updated config/routes.rb Created slices/accounts/ Created slices/accounts/action.rb Created slices/accounts/actions/.keep
This produces a very bare-bones structure under the
slices subdirectory, with an
Accounts::Action superclass and a
directory where account management actions will be created. These classes will, by convention, inherit from
Accounts::Action. By doing so, they gain behaviors specific to the application's user account area.
You may wonder what "actions" are, exactly. They are Hanami's take on organizing web endpoints. In Rails, a controller class groups a few actions (represented by public methods). However, in Hanami, you create one class per action. So an action is a standalone unit of code that can be tested in isolation and quite clearly states what it does.
Let's have a look at an example action class:
class Accounts::Actions::UpdatePassword < Accounts::Action include Accounts::Deps["commands.change_password"] before :require_authenticated params do required(:current_password).filled(:str?) required(:password).filled(:str?, min_size?: 8) end def handle(req, res) if !req.params.valid? res.status = 422 res.body = req.params.errors.to_h.join(", ") elsif change_password.call( req.session[:current_user], req.params[:current_password], req.params[:password]) res.status = 201 res.body = "password updated" else res.status = 422 res.body = "cannot update password" end end end
Here we have all the information in one place:
- The action depends on a
- It requires an authenticated user context
- It needs two parameters to be passed (and one of them must be at least 8 characters long)
- Finally, we have a
handlemethod that holds the core logic of the endpoint: checking the validity of the params, calling a dependency, and setting an appropriate response
You might be wondering what this syntax means:
This brings us to our next section about dependencies.
Accounts::Deps is a dependency mixin for the
accounts slice. Hanami makes a dependency between different classes
a first-class citizen.
Instead of having dependencies hidden in the code, you can (and should, if you want to follow Hanami philosophy) define them on top of the class. Not only does it clarify what you rely on, but it also makes the class easier to test.
In the example above, the "change password" command is defined in
Accounts::Commands::ChangePassword class. The dependency mixin will make an instance of it available as
change_password in the class where you use it. This is all based on
In tests, you can stub the dependency easily using a built-in
If, for example, your original command uses a slow-by-design hashing algorithm (such as bcrypt), you can swap it with something faster in tests.
Great Performance in Hanami
Speaking of performance, Hanami is fast. It boots really fast because providers are only booted
when needed, not eagerly at the application start. As a result, a
hanami-reloader gem (used to
reload an application after changes are made to the code in development) doesn't need a sophisticated and
fragile reloading mechanism. Instead, it just reboots the whole app in milliseconds. Simple yet effective.
But there's more on this front. Hanami 2.0 includes a brand-new router, which makes it faster than Sinatra, Grape, and Rails in benchmarks. That might not be the most important indicator for a database-heavy web application, but it's worth noting.
Hanami actions themselves can be very speedy as well. As an anecdote, a few days before the final release, Peter Solnica from the core team tweaked a logger to display the time it takes to process a request in microseconds because he was getting sub-millisecond times.
What Isn't in Hanami 2 (Yet)?
Before you start to rewrite your main application in Hanami, you should keep a few things in mind.
Version 2.0 is a bit stripped-down. As of today, the framework does not have a default view
and database layer. However, people report that adding the former is easy with the
hanami-view gem, which is almost ready.
The persistence layer can be implemented using ROM.rb.
Official support for the missing pieces is planned in the Hanami 2.1 release.
My Personal Experience with Hanami 2
I have had a test Hanami application since the beta 1 release. It is a plain old server-rendered discussion forum - think something like phpBB. The app checks how much effort it takes to add common features to a Hanami application, such as user authentication, file uploads, etc.
My experience so far has been positive. Pre-2.0, I reported some issues to the maintainers. They were super helpful and the issues were quickly fixed.
In general, even though the Hanami ecosystem lacks any "plug-and-play" solutions such as Devise, you can use many existing libraries not tightly coupled to Ruby on Rails. For authentication, you can use Warden, OmniAuth or Rodauth. For uploads there is Shrine. The pagination is built into ROM. Integration with exception catchers such as Rollbar is easy.
So far, I haven't hit any obstacles that would block me from progressing.
I am excited about the future and waiting to see what Hanami 2.1 will bring in terms of views and persistence.
The Ruby community hasn't really had more than one feature-complete full-stack web framework since Merb, so it is really refreshing to see a change in that area.
P.S. If you'd like to read Ruby Magic posts as soon as they get off the press, subscribe to our Ruby Magic newsletter and never miss a single post!