Controllers: the Rails way vs the Hanami way

Recently I have become interested in Hanami and so far I have really liked the architecture design and decisions that its author, Luca Guidi, has been taking until now.

I have decided to break the Rails vs Hanami comparison on different blog posts, one per component, in order to keep them small and concise.

This is the first post of that series, and I’m going to start with letter C from the MVC design pattern, the Controllers.

Let’s start by talking about what an actual Controller is in each framework.

The basics

Rails

Controllers are classes that are in charge of processing inbound requests, previously handled by the Router and then submit the output to the requester (client).

Each public instance method in the Controller will process a different kind of request. See the code below:

# app/controllers/users_controller.rb
class UsersController < ApplicationController
  def index; end     # GET /users
  def show; end      # GET /users/1
  def new; end       # GET /users/new
  def create; end    # POST /users
  def edit; end      # GET /users/1/edit
  def update; end    # PATCH /users/1
  def destroy; end    # DELETE /users/1
end

In this particular example, I’m showing you a RESTful controller. That’s one of the best practices when working with Rails, use RESTful wherever you can to provide access to your application Resources.

Hanami

In Hanami, Controllers are modules whose unique responsibility is to group Actions (which are the classes) that are in charge of processing a very specific inbound request, previously handled by the Router too and then submit the output to the client.

# apps/web/controllers/users/index.rb
module Web::Controllers::Users
  class Index
    include Web::Action

    def call(params)
    end
  end
end

# apps/web/controllers/users/show.rb
module Web::Controllers::Users
  class Show
    include Web::Action

    def call(params)
    end
  end
end

...

With those two Action examples, you get the idea of what’s going on.

Differences

In Rails, you have one Controller Class with as many public instance methods as you need.

In Hanami, you need one Controller Module and as many Action Classes as you need. Hanami Actions are only required to define a call public instance method.

What approach is better and why?

In my opinion, I think that the Hanami architecture is cleaner because one class per Action accomplishes the Single Responsibility Principle which states that one Class must be responsible for doing one thing.

On the other side, a really known problem with Rails’ Controllers is the trend to become really big classes over time that sometimes are hard to read and understand at first glance. Are you familiar with the Thin Controllers, Fat models “best practice”? Would it be better to not worry about getting a Fat Controller at all?

It’s pretty easy to avoid Fat Actions with Hanami. You are required to handle one request action per class, and if you find any code duplication within your Controller’s Actions, Ruby has a built-in solution for that: modules. You can just place the repeated code in a module and then include it in the Actions classes where needed. See the example below:

# apps/web/controllers/users/set_user.rb
module Web::Controllers::Users
  module SetUser
    def self.included(action)
      action.class_eval do
        before :set_user
      end
    end

    private

    def set_user
      @user = UserRepository.new.find(params[:id])
      halt 404 if @user.nil?
    end
  end
end

# apps/web/controllers/users/show.rb
require_relative './set_user'

module Web::Controllers::Users
  class Show
    include Web::Action
    include SetUser

    def call(params)
      # ...
    end
  end
end

# apps/web/controllers/users/edit.rb
require_relative './set_user'

module Web::Controllers::Users
  class Edit
    include Web::Action
    include SetUser

    def call(params)
      # ...
    end
  end
end

Solid, right?

Exposing variables to the views/templates

The Controllers are in charge of exposing data to the View layer. Hanami and Rails do this in a very similar fashion with a small difference.

Rails

In Rails, any instance variable that you declare in your controller is accessible in the templates (Views).

Hanami

In Hanami, instance variables are exposed only if you say so via the expose class method. Here is an example of it:

# apps/web/controllers/users/index.rb
module Web::Controllers::Users
  class Index
    include Web::Action
    expose :users # We are exposing the @users instance variable.

    def call(params)
      @users = UserRepository.new.all
      @another_instance_variable = {} # This will not be accesible in the view/template
    end
  end
end

Wrapping up

By now, you could say that Hanami needs more code to accomplish the same things that you can do in Rails, and it might be true. Rails architectural design relies more on Convention over Configuration, that’s why it feels like magic to work with Rails.

On the other hand, Hanami has fewer conventions and forces us to be more explicit with our intentions. In my opinion, having separate classes per Controller Action is a really good idea, because we just have to worry about what is happening on a single inbound request. And that architecture just feels right.

On the next blog post of the series, I will continue with the comparison of Rails Models (ActiveRecord) and Hanami Model Domain (Entities & Repositories)

Enums with Rails & ActiveRecord: an improved way

Overview

ActiveRecord Enums are a really good tool to use when you need that certain model’s attribute to have a finite number of possible values.

Its usage is pretty straightforward, see the example below:

class Post < ApplicationRecord
  enum status: %i[draft reviewed published]
end

In order to persist the status value to the database, we need to generate a migration using the following command:

bundle exec rails g migration AddStatusToPosts

And it should look like this:

# db/migrate/20180426164051_add_status_to_posts.rb
class AddStatusToPosts < ActiveRecord::Migration[5.2]
  def change
    add_column :posts, :status, :integer, default: 0
  end
end

It’s important to mention that, for this case, the column needs to be an integer, and it will contain values from 0 to 2. Setting the default as 0 is a good practice. Those values will mean the following:

  • 0 for draft status
  • 1 for reviewed status
  • 2 for published status

Yes, you noticed it right, the order we define the values on our **enum does matter a lot**. And if we decide to add new values, the recommendation is to add them at the end, so we don’t mess with data that we may already have in our database.

There is a way for us to override the integer number that will be used to represent a value from our enum, you can use the following approach:

class Post < ApplicationRecord
  enum status: { draft: 2, reviewed: 1, published: 0 }
end

As you can imagine, the mapping has changed to the following:

  • 0 for published status
  • 1 for reviewed status
  • 2 for draft status

Cool, enough with the “mappings”, let’s talk about the convenience methods that we get when using the enum, assuming we have the following enum declaration:

enum status: %i[draft reviewed published]

These are the methods:

post = Post.new

post.draft! # => true
post.draft? # => true
post.status # => "draft"

post.reviewed! # => true
post.draft?    # => false
post.status    # => "reviewed"
post.reviewed? # => true

Fancy, uh? As you can imagine we get the same methods for our published status.

Be aware that ! methods, change the status and also saves the record, so you need to make sure that your model meets all validations prior using the bang methods or they will fail.

Thanks to ? methods you no longer need to do comparisons to know if your record is in specific status.

The usage of enums also adds convenience scopes for us:

Post.draft     # => Collection of all Posts in draft status
Post.reviewed  # => Collection of all Posts in reviewed status
Post.published # => Collection of all Posts in published status

Pros

Besides the convenience methods and scopes that I already mentioned to you, the main advantage of using enums is that, out of the box, we get validations for the possible values that a column can have.

So the following example will raise an exception:

post = Post.new

post.status = "unknown"
=> ArgumentError ('unknown' is not a valid status)

Cons

Integer columns are hard to understand without actual context

One of the downsides of using an integer column for storing a representation of a string value, like the case of a status column, is that we are going to make it harder for people looking directly at the database table’s rows to know what number represents which status. In the previous examples, there are 3 possible statuses, but we can many more, there is no limit.

Fortunately, we can make the life easier for the people looking directly to the database.

Instead of using an integer column, you can use a string one. Your migration should look like this:

# db/migrate/20180426164051_add_status_to_posts.rb
class AddStatusToPosts < ActiveRecord::Migration[5.2]
  def change
    add_column :posts, :status, :string
  end
end

Next, you need to change your enum declaration to the following:

class Post < ApplicationRecord
  enum status: {
    draft: "draft",
    reviewed: "reviewed",
    published: "published"
  }
end

That’s it, know your posts table will have a string status column and consequently, any person looking at it will know what status the Post has.

Data Integrity issues

Now that you have already solved the readability issue in your posts table. There is still one important issue that we need to resolve. The integrity of the information within your database. Currently, it is totally possible for the people with read/write access to your database to set invalid values on your status column. As you may recall, validations are handled within your Rails application context. If we are already using enum on Rails, is because we know the values that your status column can have is finite.

Let’s add the very same constraints we have on our Rails app, to the database. To do so, native databases enums come to our rescue! I will teach you how to write migrations to generate that kind of columns in MySQL and PostgreSQL.

MySQL

The migration should look like this if you are adding the column:

# db/migrate/20180426164051_add_status_to_posts.rb
class AddStatusToPosts < ActiveRecord::Migration[5.2]
  def up
    execute <<-SQL
        ALTER TABLE posts ADD status enum('published', 'draft', 'reviewed');
    SQL
  end

  def down
    remove_column :posts, :status
  end
end

Interesting, uh? We are using MySQL’s enum() type to declare the possible values that the status column should have. You’re guessing right, those values should be exactly the same than the ones declared on your Model.

In case your column already exist, your migration should look like this:

# db/migrate/20180426164051_change_post_status_column_type.rb
class AddStatusToPosts < ActiveRecord::Migration[5.2]
  def up
    execute <<-SQL
        ALTER TABLE posts MODIFY status enum('published', 'draft', 'reviewed');
    SQL
  end

  def down
    change_column :posts, :status, :string # Previous type
  end
end

PostgreSQL

The migration should look like this if you are adding the column:

# db/migrate/20180426164051_add_status_to_posts.rb
class AddStatusToPosts < ActiveRecord::Migration[5.2]
  def up
    execute <<-SQL
      CREATE TYPE post_statuses AS ENUM ('published', 'draft', 'reviewed');
      ALTER TABLE posts ADD status post_statuses;
    SQL
  end

  def down
    execute <<-SQL
      DROP TYPE post_statuses;
    SQL
    remove_column :posts, :status
  end
end

PostgreSQL also supports enums, but we should define them first. That’s why we use the CREATE_TYPE command to define our enum, and then we add the status column and it uses the previously defined enum with CREATE_TYPE.

In case your column already exist, your migration should look like this:

# db/migrate/20180426164051_change_post_status_column_type.rb
class AddStatusToPosts < ActiveRecord::Migration[5.2]
  def up
    execute <<-SQL
      CREATE TYPE post_statuses AS ENUM ('published', 'draft', 'reviewed');
      ALTER TABLE posts MODIFY status post_statuses;
    SQL
  end

  def down
    execute <<-SQL
      DROP TYPE post_statuses;
    SQL
    change_column :posts, :status, :string # Previous type
  end
end

MySQL and PostgreSQL

Running the following commands will work as expected:

bundle exec rails db:migrate
bundle exec rails db:rollback

We will get a column on the database that will only accept the declared values. That’s the same constraint we have on our Rails app, yay!

Unfortunately, if you are still using the file db/schema.rb as your source of truth for generating/re-generating the database, there are a few drawbacks.

MySQL will give you the next result:

  create_table "posts", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t|
    t.string "title"
    t.text "content"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.string "status", limit: 9
  end

As you can see, there is nothing that indicates that your status column is an enum. That’s because Rails does not know anything about native enum column type from MySQL.

PostgreSQL’s result is even worst:

# Could not dump table "posts" because of following StandardError
# Unknown type 'post_statuses' for column 'status'

Yes, that’s what you will get in your db/schema.rb when using PostgreSQL to create an enum column on a migration. Really bad, uh?

Fortunately, the solution to this problem is the same and it is really simple: Use db/structure.sql instead of db/schema.rb!

Add this line to your config/application.rb file:

# config/application.rb
...
class Application < Rails::Application
  ...
  config.active_record.schema_format = :sql
  ...
end
...

Problem solved! Now you are safe to delete your db/schema.rb file and git track the brand new db/structure.sql. It will contain the most reliable dump of your database’s structure.

Wrapping up

ActiveRecord Enums are really useful when used correctly. We can make life easier for people with write/read access to our application’s database by using a string column instead of an integer one.

As Uncle Ben once said:

“With great power comes great responsibility”

We need to add the same constraints on our database, to guarantee data integrity. Database native enums are great for that and they keep the same readability than a string column, with the built-in protection against bad input!

Hello World!

Hi there! Welcome to my very first technical blog. I will be writting about the stuff I like to do most: programming!

This is my first step to start writing. I hope to publish content very soon. I have a bunch of topics, I just need to find some time and let the words flow!!

Eduardo Figarola