A First Look at Hanami 2 for Ruby
Tags: ruby
Published: 2022-12-15 11:57:10

_As of 06/12/2022, Hanami 2.0.1 has been released. [Read more about the enhancements, bug fixes and gems in release 2.0.1](https://hanamirb.org/blog/2022/12/06/hanami-201/)._

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:

```ruby
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 `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:

```ruby
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](https://dry-rb.org/gems/dry-system/1.0/dependency-auto-injection/).

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

```ruby
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](https://rubygems.org/gems/hanami-reloader/versions/0.2.1) (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](https://web-frameworks-benchmark.netlify.app/result?asc=0&l=ruby&order_by=level64). 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](https://ruby.social/@solnic/109358993216946670) 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](https://rubygems.org/gems/hanami-view), which is almost ready. The persistence layer can be [implemented using ROM.rb](https://guides.hanamirb.org/v2.0/introduction/getting-started/#persisting-books).

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](https://www.phpbb.com/). 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](https://rubygems.org/gems/devise/versions/4.8.1), you can use many existing libraries not tightly coupled to Ruby on Rails. For authentication, you can use [Warden](https://github.com/wardencommunity/warden), [OmniAuth](https://github.com/omniauth/omniauth) or [Rodauth](https://github.com/jeremyevans/rodauth). For uploads there is [Shrine](https://shrinerb.com/). The pagination [is built into ROM](https://hanamimastery.com/episodes/32-rom-pagination). Integration with [exception catchers such as Rollbar](https://ruby.social/@solnic/109397645329387276) 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](https://blog.appsignal.com/ruby-magic)!**