Using Hanami after a decade building Rails apps
Tags: hanami, rails, ruby, webdev
Published: 2021-01-23 16:47:29

I remember exactly where I was when I first watched DHH's Blog in 15 Minutes video. At the time, I was mostly using PHP for my projects, and what I saw blew my mind. 🤯 

{% twitter 492706473936314369 %}

I immediately became fascinated with Ruby on Rails. I spent the next couple years of my life working a Java job while learning Ruby on Rails on the side. Eventually, I learned enough to make the career leap from Java to Rails and I spent the subsequent 10 years working on Rails apps.

Recently, I had an idea for a side project, and I decided to build it with Hanami instead of Rails. I chose Hanami for a few reasons:

1. I had some extra free time and wanted to learn something new
2. My Rails apps have lately been trending towards the use of lots of small POROs and service objects
3. The architecture of a Hanami app feels like a natural progression from Rails

In this article, I'm going to show you some of the things I learned and really liked about Hanami.

## The Project

The State of Mississippi provides a daily table with the latest counts of total COVID cases and deaths in each county. 

![MSDH table](https://dev-to-uploads.s3.amazonaws.com/i/s2jy9y2g2nthtfpxlu4n.png)

Since the table only shows the *total* cases and deaths, you don't get a picture into the daily increase in cases and deaths for each county.

I decided to scrape the table each day and subtract yesterday's totals from today's totals in order to get the daily numbers. 

![Screenshot of my project](https://dev-to-uploads.s3.amazonaws.com/i/whapy4597ijxpvuaxrwj.png)

I also stored the table each day so there is also a historical view of the data.

![Screenshot of county page](https://dev-to-uploads.s3.amazonaws.com/i/t751mqsagso4cj3x3yhr.png)

I stored the county name in a `counties` table and I had a second table called `county_updates` that looked like this:
- `cases`
- `deaths`
- `ltc_cases`
- `ltc_deaths`
- `county_id`
- `previous_update_id` which is a self-referential foreign key

## Implementation

### Repositories and Entities

Hanami splits your typical Rails model into a repository class and an entity class. A repository class is where your database queries live and an entity class is a representation of your data. The entity class has attributes auto-mapped from your table schema.

I added a `CountyUpdate` entity that added a few calculation methods on top of the auto-mapped attributes.

```ruby
class CountyUpdate < Hanami::Entity
  def new_cases
    return unless previous_update

    cases - previous_update.cases
  end

  def new_deaths
    return unless previous_update

    deaths - previous_update.deaths
  end

  def new_ltc_cases
    return unless previous_update

    ltc_cases - previous_update.ltc_cases
  end

  def new_ltc_deaths
    return unless previous_update

    ltc_deaths - previous_update.ltc_deaths
  end

  def new_cases_percent_change
    return unless previous_update

    (new_cases.to_f / previous_update.cases.abs * 100).round(1)
  end
end
```

One advantage of this approach is that your queries are isolated and easily testable.

```ruby
RSpec.describe CountyUpdateRepository, type: :repository do
  let(:repo) { CountyUpdateRepository.new }

  describe '#find_latest_by_county_id' do
    it 'finds the latest update for a county' do
      build_stubbed(:county, :with_updates)

      latest_date = repo.find_latest_by_county_id(1).date.to_date
      expect(latest_date).to eq Date.today
    end
  end
end
```

### Controller Layer

In Hanami, each route corresponds to an Action class. This contrasts with Rails, where each route corresponds to a method within a Controller class.

In my app, I have a route that lists all of Mississippi's counties.

```ruby
get '/counties', to: 'counties#index'
```

This class is invoked when the `/counties` route is hit.

```ruby
module Web
  module Controllers
    module Counties
      class Index
        include Web::Action

        expose :counties

        def call(params)
          @counties = CountyWithLatestUpdateRepository.new.all
        end
      end
    end
  end
end
```

Likewise, for the `show` route, there's a corresponding `Show` action.

```ruby
module Web
  module Controllers
    module Counties
      class Show
        include Web::Action

        expose :county

        def call(params)
          @county = CountyRepository.new.find_by_name_with_updates(params[:name])
        end
      end
    end
  end
end
```

I really, really like this approach. 

Rails controllers are often cluttered and contain extraneous code that is relevant to only certain actions. For example, why is the code to permit `params` for the `create` action in the same class as the `index` action?

The Hanami approach adheres to the Single Responsibility Principle. You can be assured that any code in an action class is only relevant to that endpoint. It makes your actions dead simple to test.

### Apps

Another one of my favorite things about Hanami is that you can have multiple apps that use your business logic and present it in different ways. Your business logic lives in the `lib` directory and you can access this logic from multiple apps within the `app` directory. When you create a new Hanami application, you get a server-side rendering app that lives in `app/web`.

I didn't fully understand how awesome apps are until I actually needed to create a second app. After a short exchange with someone on Twitter, I offered to make the data for this project available via API.

I generated a new app in `app/api` using the `hanami` generator:

```bash
bundle exec hanami generate app api
```

Then I simply added a controller that rendered the JSON.

```ruby
module Api
  module Controllers
    module Counties
      class Show
        include Api::Action

        def call(params)
          county = CountyRepository.new.find_by_name_with_updates(params[:name])
          self.body = JSON.dump(county.to_h)
          self.format = :json
        end
      end
    end
  end
end
```

I had a functional API in minutes.

Unlike Rails, the API was purely additive code. I didn't have to touch any existing code to handle different content types. I didn't have to write any `respond_to` blocks. If I wanted to, I could've used two distinct queries for my JSON and HTML responses without branching logic.

## Takeaways
My biggest takeaway from Hanami is that I felt like my code quality was improved over one of my typical Rails projects.

I've been using Rails for over a decade and sometimes I still struggle to figure out where I should put code. "Does this code belong in a model? At what point should I move model code to a service object?" I encountered this exact scenario on a Rails project recently. I moved a method back and forth between a model and PORO several times before making up my mind, and still wasn't happy with where it ended up. I eventually decided to cut my losses and just left it where it was.

I have not encountered such an issue in Hanami. It almost feels like the framework *enforces* code quality. It definitely feels like it makes it harder to write bad code. 

There's not a whole lot of magic going on. The framework is there to guide you, not surprise you.

I was really impressed with the way this project turned out. There was a bit of a learning curve in some cases, but I was able to get over the hump. [Hanami 2.0 is in the works](https://timriley.info/writing/2020/05/07/sharing-my-hanami-2-application-template/) and I'm excited to see what it brings.

### P.S.

If you're interested in checking out the app, it's running on [heroku @ https://ms-covid-tracker.herokuapp.com/](https://ms-covid-tracker.herokuapp.com/)

Code is on [github](https://github.com/mculp/ms-covid-tracker)

I'm on twitter [@_mculp](https://twitter.com/_mculp)