Handling nil in method calls
On my current project, we noticed common pattern when dealing with nil. We would often check an object to see if it was nil before calling a method on that object:
name = person ? person.name : nil
To reduce duplication, Patrick Farley and Ali Aghareza created a nil_or method which handles this. The above code becomes:
name = person.nil_or.name
The nil_or causes the expression to return nil if the target is nil. If not, the name method is called.
The code for nil_or looks like:
module ObjectExtension
def nil_or
return self unless self.nil?
Class.new do
def method_missing(sym, *args); nil; end
end.new
end
end
class Object
include ObjectExtension
end
The nil_or method returns self if self is not nil. If self is nil, it creates a new Object which eats all method calls and returns nil.
We use a fair amount of delegation on this project using forwardable, so Michael Schubert and Toby Tripp created a delegator which has the same effect. For example, you can replace this delegation:
class Person
extend Forwardable
def_delegator :@job, :title, :job_title
end
with this one:
class Person
extend Forwardable
def_delegator_or_nil :@job, :title, :job_title
end
This delegation is equivalent to this code:
class Person
def job_title
@job ? @job.title : nil
end
end
The code for def_delegator_or_nil looks like:
module ForwardableExtension
def def_delegator_or_nil(accessor, method, new_method = method)
accessor = accessor.id2name if accessor.kind_of?(Integer)
method = method.id2name if method.kind_of?(Integer)
new_method = new_method.id2name if new_method.kind_of?(Integer)
module_eval(<<-EOS, "(__FORWARDABLE_EXTENSION__)", 1)
def #{new_method}(*args, &block)
begin
if #{accessor}.nil?
nil
else
#{accessor}.__send__(:#{method}, *args,&block)
end
rescue Exception
$@.delete_if{|s| /^\\(__FORWARDABLE_EXTENSION__\\):/ =~ s} unless Forwardable::debug
Kernel::raise
end
end
EOS
end
end
module Forwardable
include ForwardableExtension
end