Stack Builders News

A collection of thoughts and notes by our team


Filipe Costa

Null Object Pattern


A design is far better when there are no exceptions. Whenever you write an if statement, Nyan Cat cries.

Yesterday I remembered this blog quote when I faced a common situation handling nested hashes in Ruby.

hash = {
  company_name: "Stack Builders",
  address: {
    line_1: "315 Fifth Avenue",
    line_2: "Suite 703A",
    city: "New York",
    state: "NY",
  },
}

What if I want to get some of that data and use it to manually instantiate a Company entity? Wll, attributes for that entity are not nested like the hash is:

Company.new(
  name: hash.fetch(:company_name),
  address_line_1: hash.fetch(:address).fetch(:line_1),
  address_line_2: hash.fetch(:address).fetch(:line_2),
  city: hash.fetch(:address).fetch(:city),
  state: hash.fetch(:address).fetch(:state)
)

That's not the most beautiful piece of code, but it works if--and only--if all keys are present. If not fetch will complain. You can say, "But what if we use [] method instead of fetch?" Well, if the address key is missing in our hash, the code hash[:address][:line_1] will throw a NoMethodError for NilClass. Not ideal.

What now? "Add an if, it will fix that." Mmmm... yes, it will solve the problem, but do you remember the quote at the beginning of this post? I want to avoid adding if statements to this piece of code. This is where the Null Object pattern enters.

I used a very simple implementation of this pattern just to satisfy cases like this one. We can use it in many different ways and have a lot of flexibility, I just wanted to avoid the NoMethodError being thrown again.

# null_object.rb
class NullObject
  def method_missing(*args, &block)
    nil
  end
end


# company.rb
address = hash.fetch(:address, NullObject.new)
Company.new(
  name: hash.fetch(:company_name),
  address_line_1: address.fetch(:line_1),
  address_line_2: address.fetch(:line_2),
  city: address.fetch(:city),
  state: address.fetch(:state)
)

This way the NullObject instance is just nullifying my call to fetch, because I don't have that :address key. This improved my design, simplified my code, and made it safer.

Why don't you go ahead give it a try? It might be as helpful for you as it was for me. Here I created my own implementation of this pattern, a very simple one, no fanciness needed, but if you want something more sophisticated check out Naughy, by Avdi Grimm.

Do You Have What it Takes To Be a Stack Builder?