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
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 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
that class would have held as well! Our updated
Guest class would look like
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.
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.