Using conditional statements to check for nil values may feel natural. However, doing so introduces a code smell. There is a better alternative.

Let’s start with a simple example:

user.posts.last.publish

What could go wrong?

NoMethodError: undefined method publish for nil:NilClass

What can we do about it?

user.posts.last.publish unless user.posts.last.nil?
# or
user.posts.last.publish if user.posts.last
# or
user.posts.last.publish if user.posts.any?
# or
user.posts.last && user.posts.last.publish
# or
user.posts.last.try(:publish)
# or
user.posts.last&.publish

And so on. What is the problem then? These checks show that we are not confident in our code. This lack of confidence is leading to a cluttered code. Cluttered code leads to more bug, more difficult maintenance and more difficult refactorings.

Checking for Nulliness is a Type Checking

It’s not a surprise that in Ruby (almost) everything is an object. nil is not an exception. nil is an instance of NilClass. When we are checking for nil, we are doing a type checking.

user.posts.last.publish unless user.posts.last.nil?

is equivalent to:

user.posts.last.publish unless user.posts.last.is_a?(NilClass)
# or
user.posts.last.publish unless user.posts.last.kind_of?(NilClass)

We all know that checking type of objects at runtime is not a good practice. Ruby encourages the use of polymorphism and duck typing instead.

So, why would we check if an object is nil before calling a method on it?

Conditional Statements Everywhere

The biggest concern is that our nil checks will probably be everywhere in our codebase.

This brings 3 major problems:

  • Duplicated code
  • Readability impacted
  • More complicated refactorings

Our Issue With NilClass

Let’s take a look at our example again:

user.posts.last.publish unless user.posts.last.nil?

We are checking if the last post is nil because we know that nil does not respond to the publish message.

As a side note, using respond_to? would be more appropriate rather than checking if the type of the object is NilClass:

user.posts.last.publish if user.posts.last.respond_to?(:publish)

In fact, we would like last to respond to publish no matter what, so we could avoid the conditional.

Here Comes the Null Object

The goal of the Null Object is to help dealing with uncertainty. By using this pattern we are assured that a given object will respond to a given method. Thus, we don’t need to check if the said object is nil or not, we just have to send it a message.

The Null Object should respond to the same public interface as our original object. Thanks to Duck Typing, we will be able to use them indifferently.

We introduce a class that responds to the same public API as Post:

class NullPost
  def publish
    # do nothing
  end
end

First refactoring:

last_post = user.posts.last || NullPost.new
last_post.publish

We could argue that using || is still some sort of a conditional statement. However we are not explicitely checking if last_post can respond to publish. We are certain that it will, no matter what. We are more confident in our code.

More Refactoring

We can go a step further in the refactoring. We removed the clutter caused by the if statement, but we introduced clutter with the last_post variable and the ||. Also, we will always have a duplication problem because we will have to build our Null Object everytime we want want to call publish.

Let’s fix this problem by building the Null Object only at one place:

class User
  has_many :posts

  def last_post_or_null
    posts.last || NullPost.new
  end
end

And now we simply have:

user.last_post_or_null.publish

A Note on “Nothing”

Usually, when a Null Object recieves a message, it responds nothing in return. However “responding nothing” may vary depending the context. Some people like to use the Null Object to produce a placeholder response.

Before:

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

After:

class NullUser
  def name
    'Guest'
  end
end

def current_user_or_null
  current_user || NullUser.new
end
Hello <%= current_user_or_null.name %>

Rendering Null Objects

Let’s try to render our last post:

<%= render user.posts.last %>

What could go wrong in this case?

nil is not an ActiveModel-compatible object. It must implement :to_partial_path

Bad solution:

<% if user.posts.last.present? %>
  <%= render user.posts.last %>
<% end %>

We now have our 3 problems again:

  • Duplicated code
  • Readability impacted
  • More complicated refactorings

First step: let’s use our NullPost:

<%= render user.last_post_or_null %>

#<NullPost:0x007f5bfdceae40> is not an ActiveModel-compatible object. It must implement :to_partial_path.

This errors means that render expects its parameter to respond to to_partial_path. A quick search in the documentation leads us to the ActiveModel::Conversion module:

class NullPost
  include ActiveModel::Conversion

  # NullPost are never persisted in the DB
  def persisted?
    false
  end

  def publish
    # do nothing
  end
end

We get this error:

Missing partial null_posts/_null_post with {:locale=>[:en], :formats=>[:html], :variants=>[], :handlers=>[:raw, :erb, :html, :builder, :ruby, :jbuilder, :haml]}

Pretty straight forward: we just have to create the null_posts/_null_post.html.erb partial. In our case we are going to leave the file empty. However, remember that “responding nothing” may vary depending the context.

Now we can render user.last_post_or_null with confidence.

Generic Null Object

Null Object is one of the simplest pattern: accept all messages and do nothing with it. How can we take advantage of Ruby’s features to build a generic Null Object?

class NullObject
  include ActiveModel::Conversion

  def method_missing(*)
    # do nothing
  end

  def respond_to?(name)
    true
  end

  def persisted?
    false
  end
end

We will also need to create an empty partial: null_objects/_null_object.html.erb.

NullPost.new can be replaced with NullObject.new.

Drawbacks

Everything comes at a cost. Here are some drawbacks of Null Objects:

  • We have another class to maintain (and another view if the object can be rendered)
  • It will be tempting to duplicate behavior from Post to NullPost
  • Sometimes we might want to distinguish between Post and NullPost/NullObject.