I’ve recently come to dislike nil a lot. In one of our projects we have something like this:
<tr> <td> <%= Client.human_attribute_name(:vat_number) %> </td> <td colspan="3"> <%= @client.try(:vat_number) %></td> </tr> <tr> <td> <%= Client.human_attribute_name(:company_ownership_type_id) %> </td> <td colspan="3"> <%= @client.ctry(:company_ownership_type, :name_bg) %></td> </tr> <tr> <td> <%= Client.human_attribute_name(:company_branch_id) %> </td> <td colspan="3"> <%= @client.ctry(:company_branch, :name_bg) %></td> </tr>
This bugs me to no end. The worst part is that actually I wrote ctry, which is a chained try . It seems really dirty and I’ve been looking for a solution for quite some time. The problem with the given code is actually that it breaks the Law of Demeter and can be solved with a few delegate declarations, but it lead me to my discovery of the Null Object Pattern.
Imagine you have an application and you have to print a greeting to the current user, something like “Welcome, George”. The first thing you try may be:
Welcome, <%= current_user.name %>
But then you or someone else logs out and gets this dreaded application error:
NoMethodError: undefined method `name' for nil:NilClass
You quickly fix your code by adding a nil check and also decide to greet logged out people:
Welcome, <%= current_user.nil? ? "Guest" : current_user.name %>
This works as you originally intended and is good enough for production. Or is it? You start using the same pattern in different places, your collegues also pick up on it and a few months later you have a filthy mess that is really hard to touch and refactor.
Your next problem comes with a change in the requirements - guest users are not to be greeted as “Guest”, but as “Stranger”, because your boss thinks it’s funnier. You start working on your task and find that there are 78 occurances of the string “Guess” in your views and you need to change each one separately because you cannot mass replace such a common word as ‘Guest’. You curse and get on with it, because this is life and life sucks.
Could this been avoided? Yes, and the solution is really simple.
What we need is way to make
current_user respond to
name 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
NullUser class should conform to the same public API as the
which is not pleasant and requires maintenance, but everything comes at a cost.
Next you change your
current_user method to return a new instance of
if no user is logged in:
def current_user current_user_session || NullUser.new end
Now when your new requirements arrive, you only need to update the
NullUser class and you need not to worry about breaking anything else.
If you dislike the name of the class, you may also use
it doesn’t matter.
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 nil. 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.
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.