Thank you to Ben Hamill for sending me a question that prompted this post.
Checking for object presence
Very often in Ruby code, we would like to execute some action only if an object is present:
def slug(title) if title title.strip.downcase.tr_s('^[a-z0-9]', '-') end end
slug(" Confident Code")
confident-code
h = {}
slug(h[:missing_key])
nil
Strictly speaking, we aren’t checking for object presence here. In Ruby there is almost always an object present, but the default marker for missing data is the special nil object—the one and only instance of NilClass.
h = {}
def noop; end
| Expression | Result |
|---|---|
h[:missing] |
nil |
noop |
nil |
if (1==2) then 'wrong' end |
nil |
What we are really checking for is the presence of either a “falsy” object (nil or false, most likely nil), or a “truthy” object (anything other than nil or false). So in effect this is an instance of typecasing.
Switching on object type is a smell in object-oriented languages. In general, we don’t want to ask an object what it is; we want to tell it what to do and let it figure out the best way to do it based on its type. This is the essence of polymorphism.
What we need is an object to represent the special case of “do nothing on missing value”. As it happens, there is a pattern for that: Null Object. Quoting Wikipedia: “a Null Object is an object with defined neutral (‘null’) behavior”.
Ruby does not have a built-in Null Object class (NilClass doesn’t qualify). Implementation of one is trivial, however:
class NullObject def method_missing(*args, &block) nil end end
Instances of NullObject will respond to any message (method call) with a no-op.
no = NullObject.new
no.foobar
nil
A more useful (and common) form of Null Object returns self from every call.
class NullObject def method_missing(*args, &block) self end end
This version makes it possible to nullify arbitrary chains of method calls:
NullObject.new.foobar.baz.buz
#<NullObject:0x7f97b56214f8>
A useful accompaniment to a Null Object class is a constructor method that converts nil values into null objects, and leaves other values as-is:
def Maybe(value) case value when nil then NullObject.new else value end end
We can also define some common conversions a la NilClass:
class NullObject def to_a; []; end def to_s; ""; end def to_f; 0.0; end def to_i; 0; end end
With these tools in hand we can rewrite our #slug method more cleanly, and, dare I say, more confidently:
def slug(title) Maybe(title).strip.downcase.tr('^[0-9a-z]', '-') end
puts slug(" Exceptional Ruby ").to_s # => "exceptional-ruby" puts slug(nil).to_s # => ""
In some cases we may want to call methods on other objects if the maybe-null object is not null. For this case, we can define the Ruby 1.9 / ActiveSupport #tap method as a no-op for NullObject:
class NullObject def tap; self; end end
Maybe(obj).tap do puts "Object is present!" end
The code in the #tap block will not be executed if the object is nil.
But is it falsey?
Still, even with a null object replacing nil there may be times when we want to check whether the expected object is present or not.
user = Maybe(User.find(123)) # ... if user "You are logged in as #{user.name}" else "You are not logged in" end
You are logged in as
Hm, that’s not what we wanted. There is no user, but the NullObject standing in for the user is “truthy”, because it’s not false or nil.
This is particularly surprising when we are branching based on the value of a predicate:
if user.subscribed? "Secret subscription-only stuff!" end
Secret subscription-only stuff!
What’s going on here? The result of the call to #subscribed? is neither true nor false; it is the NullObject instance. Which, because of Ruby’s semantics, is truthy.
As it turns out, it is not possible in Ruby to make our own objects “falsey”. We can get close:
class NullObject # All Ruby objects define the #nil? predicate def nil?; true; end end
if !user.nil? "You are logged in as #{user.name}" else "You are not logged in" end
You are not logged in
In a program using ActiveSupport we might also define a few more common predicates:
class NullObject def present?; false; end def empty?; true; end end
In Ruby 1.9 we can get even closer to the ideal of user-defined falsiness by implementing the ! (negation) operator:
class NullObject def !; true; end end
if !!user "You are logged in as #{user.name}" else "You are not logged in" end
But still the goal of being able to treat our NullObject like a common nil eludes us.
We might also try basing NullObject on NilClass or FalseClass in order to inherit their falsiness. Unfortunately this too is impossible; NilClass and FalseClass are not allocatable, meaning it is not possible to create new objects of those classes (or any derivative of them).
class NullObject < NilClass # NilClass has no .new defined, so we have to recreate the default # implementation def self.new o = allocate o.initialize o end def method_missing(*args, &block) self end end no = NullObject.new # => raises a "no allocator defined" error
We could try another tack. We could define a function to “resolve” the null object back to a nil when needed:
def Value(object) case object when NullObject then nil else object end end
Now we when we can wrap our maybe-null object with a Value() call to get the object we need:
if Value(user) "You are logged in as #{user.name}" else "You are not logged in" end
If we don’t mind extending core classes we could make this an instance method instead:
class Object def to_value self end end class NullObject def to_value nil end end
if user.to_value "You are logged in as #{user.name}" else "You are not logged in" end
You are not logged in
Chasing after the wind
Let’s take a step back. Why do we care if the value is falsey? Because we want to handle that case differently. A lot of the time, the way we want to handle the absence of an object is to do nothing, and that’s the strategy that Null Object represents.
But here we want to do something when the value is missing; we want to return a different string. Maybe the problem isn’t really one of making NullObject act falsey. Maybe this isn’t the right scenario for a Null Object at all.
Let’s instead throw together a simple Presenter implementation:
require 'delegate' class UserPresenter < SimpleDelegator def login_status "You are logged in as #{name}" end end class NilClassPresenter < SimpleDelegator def login_status "You are not logged in" end end def Present(object) presenter_class = Object.const_get("#{object.class.name}Presenter") presenter_class.new(object) end
(In a real app the presenters would presumably have many more methods.) Let’s see how the code looks using presenters:
user = User.find(123) # ... Present(user).login_status
You are not logged in
Ah, there we go. Back to good old polymorphism.
Conclusion
If we’re trying to coerce a homemade object into acting falsey, we may be chasing a vain ideal. With a little thought, it is almost always possible to transform code from typecasing conditionals to duck-typed polymorphic method calls. All we have to do is remember to represent the special cases as objects in their own right, whether that be a Null Object or something else.





