The Null Object Pattern is a great tool for removing conditionals in your code base. Rather than checking for nil or predicates about an object existing, you instead return a “null” implementation that responds to the same interface. The most common case I’ve applied this to in my Rails apps is the concept of a “Guest User”. For example:
class Guest
def email
""
end
def admin
false
end
alias_method :admin?, :admin
def purchases
[]
end
end
class ApplicationController < ActionController::Base
include Clearance::Controller
def current_user
super || Guest.new
end
end
This implementation can serve us quite well. Our code base expands, blissfully
unaware of whether it’s dealing with a User
or a Guest
. As we add logic for
our store front, we end up with methods like this:
class StoreListing < ActiveRecord::Base
def included_in?(purchases)
purchases.map(&:store_listing_id).include?(:id)
end
end
Things will continue to work on our empty array, as long as we are only calling
methods from the Enumerable
module. However, we run into problems as soon as
we write some code like this:
class CategoryStoreController < ApplicationController
def index
redirect_to(subcategory_store_path(subcategory_for_redirect))
end
private
def subcategory_for_redirect
last_purchase_in_category.subcategory || category.subcategory.first
end
def last_purchase_in_category
@last_purchase_in_category ||= current_user.purchases.last_in_category(category)
end
def category
@category ||= Category.find(params[:id])
end
end
If the user isn’t logged in, this will fail with NoMethodError: undefined
method 'last_in_category' for []:Array
.
Rails to the Rescue
Luckily, Rails 4.0 introduced a new method on ActiveRecord::Relation
to help
with exactly this situation! Relation#none is a method that
will return a new instance of NullRelation
.
NullRelation
responds to every method that a normal instance of
Relation
would. As a bonus, when you call .none
on one of your model
classes, it will also respond to all of the class methods that a Relation
for
that class would have held as well! Our updated Guest
class would look like
this:
class Guest
def purchases
Purchase.none
end
end
Once we’ve utilized Relation#none
in our null objects, the rest of our code
works as expected, and we can continue blissfully unaware of the existence of
Guest
in the rest of our codebase.
Backporting to Rails 3
For those of you who haven’t upgraded to Rails 4 yet (you probably should…), you can achieve a similar effect with this simple back-port:
class ActiveRecord::Base
def self.none
where("1 = 0")
end
end
This isn’t quite the same as NullRelation
. It’ll still hit the database and
try to load data, and the call to none
could be undone with
.unscope(:where)
. However, for most cases it should act as a suitable polyfill
until you’re able to upgrade to Rails 4.
What’s next
If you enjoyed this article, you might also enjoy:
- Ruby Science for more examples of refactoring patterns in Ruby.
- Rails Refactoring Example: Introduce Null Object for an example of the Null Object Pattern in action.
- Testing Null Objects for examples of how to test this pattern.