The Null Object pattern


Every Ruby programmer eventually encounters the dreaded NoMethodError exception.

undefined method `first_name' for nil:NilClass

We learn to live with it and handle it in various nasty ways. Like using try:

tr
  td = Client.human_attribute_name(:vat_number)
  td = @client.try(:vat_number)
tr
  td = Client.human_attribute_name(:company_ownership_type_id)
  td = @client.ctry(:company_ownership_type, :name_bg)
tr
  td = Client.human_attribute_name(:company_branch_id)
  td = @client.ctry(:company_branch, :name_bg)

This code is bad because our @client should never actually be nil. We can improve it by using the Null Object Pattern.

An example

Imagine we have an application and have to print a greeting to the current user, something like “Welcome, George”. The first thing we try may be:

Welcome, <%= current_user.name %>

But then we log out and are greeted with an old friend:

NoMethodError: undefined method `name' for nil:NilClass

We quickly fix our code by adding a nil check and also decide we want to greet logged-out people too:

Welcome, <%= current_user.nil? ? 'Guest' : current_user.name %>

This works as we originally intended and is good enough for production. Or is it? We start using the same pattern in different places, our collegues also pick it up and a few months later we find ourselves in with filthy mess that is really hard to refactor.

Our next problem comes with a change in the requirements - guest users are not to be greeted as “Guest”, but as “Stranger”, because our boss thinks it’s funnier. We start working on our task and find that there are 78 occurances of the string “Guess” in our view code and we need to change each one separately because we cannot mass replace such a common word as ‘Guest’. We curse the gods and get on with it, because this is life and life sucks.

Could this have been avoided? Yes, and the solution is really simple.

The pattern

What we need is way to make current_user respond to the name method even if no user is currently logged in. We start by introducing a new class:

class NullUser
  def name
    'Guest'
  end

  def logged_in?
    false
  end
end

The NullUser class should conform to the same public API as the User class, which requires maintenance, but everything comes at a cost.

Next, we change our current_user method to return a new instance of NullUser when no user is logged in:

def current_user
  current_user_session || NullUser.new
end

Now when our new requirements arrive, we only need to update the name method of the NullUser class and need not to worry about breaking anything else. If we dislike the name of the class, we may also use Guest or GuestUser, it doesn’t matter.

Drawbacks

The Null Object is not a silver bullet and not something you want to use for all your classes. It should not be used to avoid all nil errors but as a way to provide default behaviour when you get an unexpected nil. Sometimes nil is just nil and should be handled as such. You should also have an extensive test suite that ensures that nowhere a nil may be assigned instead of your Null Object instance, because that will break wreak havoc on your happy little world.

Last words

The pattern really shines in the cases it’s is suited for, such as the NullUser or representing the last element of a list/tree. Apply it with care and it may improve your life a little.

Related Posts

The Safe Navigation Operator (&.) in Ruby

Ruby 2.3 introduces the &. operator which will make dealing with nils easier

Benchmarking Ruby

How to benchmark ruby code and compare different solutions to a problem

Rails migration generator - how to specify decimal precision and scale

Hello World

Every techie's first post