The null object pattern

Ruby on Rails / Refactoring

Null Object refactoring pattern jumps into action when we have cases where we are checking if given object is present and if it’s not then we are returning the default value for given attributes or methods. It usually requires us to use if condition which makes code a little bit less readable and a little bit harder to test – with Null Object pattern code is very simple and easy to test.

To better demonstrate advantages of using Null Object pattern let’s consider following case: we have two classes, User object which operates on Post class.

class User < ActiveRecord::Base
  has_many :posts

  def latest_post_title
    post = posts.order('created_at DESC').first

    if post.present?
      post.title
    else
      "No posts yet"
    end
  end
end

It’s far from single responsibility principle - we are doing a few actions here:

  1. We fetch the newest post
  2. We are checking if the post is present
  3. We are displaying post title if the post is present
  4. We are displaying proper information when the post is not present

This is the time when Null Object comes in. Let’s create our new object first:

class NoPost
  def title
    "No posts yet"
  end
end

It’s just a plain Ruby object with simple logic. Now it’s time to refactor our User model by doing a couple things:

  1. We will move query to the separated method
  2. We will move latest post assignment to the separated method using NoPost Null Object
  3. We would simplify method responsible for returning latest post title

After implementing all of these points, our User class is way cleaner and more readable:

class User < ActiveRecord::Base
  has_many :posts

  def latest_post_title
    lastest_post.title
  end

  private

  def latest_post
    find_latest_post || NoPost.new
  end

  def find_latest_post
    posts.order('created_at DESC').first
  end
end

The contents of our User#latest_post_title method is obvious now and we get rid of if condition. It’s also very important to name our Null Object properly so its name reflects the idea behind it.