Skip to Content
Course content

197: Comparing Hanami to Rails

Click on the "Edit" button in the top corner of the screen to edit your slide content.

I've spent a lot of time chatting with Ruby devs who are looking for an alternative to Rails, and there is one phrase I hear constantly: "I want to try Hanami because it's a lightweight version of Rails."

Here is the problem: if you go into Hanami thinking it's just "Rails Lite," you're going to be frustrated. You'll spend your first three days wondering why you can't just add a method to a model and have it magically work everywhere. Hanami isn't just Rails with fewer gems; it is a fundamentally different philosophy of how an application should be structured. Rails is built on the "Omakase" principle—the framework makes the big decisions for you. Hanami is built on the principle of decoupling.

"Hanami is just a smaller Rails" vs. "Hanami is an architectural shift"

In Rails, the Model is the center of the universe. Thanks to ActiveRecord, your model is a "God Object"—it handles database schema, validations, persistence, and business logic. It's incredibly fast to build, but as your app grows, those models become bloated messes of a thousand lines.

Hanami splits those responsibilities apart. Let's look at a simple "User" implementation. In Rails, you'd have one file:

# Rails: app/models/user.rb
class User < ApplicationRecord
  validates :email, presence: true
  
  def full_name
    "#{first_name} #{last_name}"
  end

  def self.active
    where(active: true)
  end
end

In Hanami, that one file is split into at least two different concepts: an Entity and a Repository. The Entity is a simple data object (it doesn't know the database exists), and the Repository is the only place where SQL is actually executed.

# Hanami: lib/my_app/entities/user.rb
module MyApp
  class User < Hanami::Entity
    def full_name
      "#{first_name} #{last_name}"
    end
  end
end

# Hanami: lib/my_app/repositories/user_repository.rb
module MyApp
  class UserRepository < Hanami::Repository
    def active
      users.where(active: true).to_a
    end
  end
end

I'll be honest: this feels like more work upfront. You're writing more files. But the payoff happens six months later. Because the User entity is just a plain object, you can test your business logic (like full_name) without ever hitting the database. Your tests become lightning fast.

"Convention is always better" vs. "Explicitness prevents surprises"

Rails relies heavily on "magic." You define a route, and Rails magically finds the controller action based on a naming convention. You call a method, and it's mixed in from a hidden concern. I love this for prototyping, but it can be a nightmare when you're debugging a complex production bug and can't find where a method is actually defined.

Hanami 2.0 doubles down on Dependency Injection. Instead of relying on global state or magic constants, Hanami encourages you to "import" the tools you need into your actions. It uses a container system (via the dry-system gem) to manage these dependencies.

When you look at a Hanami action, you can see exactly what it relies on. There's no guessing. If an action needs the UserRepository, it asks for it explicitly. This makes your code significantly easier to mock during testing and prevents the "spaghetti" effect where changing one line in a Rails model unexpectedly breaks a view helper three folders away.

So, which one should you use? If you need to ship a MVP in two weeks and you're the only dev, Rails is hard to beat. But if you're building a long-term system where maintainability and test speed are more important than initial setup speed, Hanami's decoupled approach is a breath of fresh air.




📋 Practical Task

Refactoring a Rails-style Model into Hanami Components

You have been handed a legacy Rails-style Product model that has become too bloated. Your goal is to refactor this logic into a Hanami-style structure by separating the Data Entity (business logic) from the Repository (persistence logic).

The Legacy Code:

class Product < ApplicationRecord
  # Database logic
  def self.out_of_stock
    where("inventory_count <= 0")
  end

  # Business logic
  def discounted_price
    price * 0.9
  end
end

Your Task:

  1. Create a Product entity class that inherits from Hanami::Entity and contains the discounted_price method.
  2. Create a ProductRepository class that inherits from Hanami::Repository and contains the out_of_stock method.
  3. Ensure the out_of_stock method uses the repository's internal query interface (e.g., products.where(...)) rather than calling a class method on the entity.
Rating
0 0

There are no comments for now.

to be the first to leave a comment.