ruby

A First Look at Hanami 2 for Ruby

Paweł Świątkowski on

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:

• Slices
• Dependency management
• Performance

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 app directory.

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
before :require_authenticated

params do
end

def handle(req, res)
if !req.params.valid?
res.status = 422
res.body = req.params.errors.to_h.join(", ")
req.session[:current_user],
res.status = 201
else
res.status = 422
end
end
end

Here we have all the information in one place:

• The action depends on a change_password command
• 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 handle method 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:

include Accounts::Deps["commands.change_password"]

This brings us to our next section about dependencies.

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 slices/accounts/commands/change_password.rb as an 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 dry-system.

In tests, you can stub the dependency easily using a built-in stub method:

Accounts::Container.stub("commands.change_password", FakePasswordChanger.new)

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.

Wrapping Up

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.

Happy coding!

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!