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
16 Responses to “Handling nil in method calls”
Sorry, the comment form is closed at this time.




What if you extend the NilClass with method_missing so if you call person.name it will return nil if person is nil – or is this a bad idea?
This would also work:
person = person.name if person
…I meant:
name = person.name if person
@Daniel: That would conflict with the “whiny nil” behavior already built into Rails.
@Paul: ahem.
Daniel, that would also affect nil everywhere, whereas nil_or only affects the place we use it. If we change nil, it may be hard to track down places where we get unexpected nils.
Hey, thanks for this post, I’m fairly new to rail and ruby. I’ve been wondering how others handle nil issues.
This is nice and DRY. In the example, you only have to mention ‘person’ once. This is even more important when ‘person’ is a more complicated expression.
///ark
another way without ?:
person && person.name
I think this would cause a memory leak because it creates a new class each time #nil_or gets called. That class wouldn’t be garbage collected. Instead, you should create a new object and define method_missing on its metaclass:
There are much simpler ways to do this: http://blog.evanweaver.com/articles/2008/01/25/safe-nils-in-ruby/
Alek, your example only works in views. If you remove the conditional, it works everywhere, but then you’ve changed nil in your entire app. I like nil_or because it is more intentional. We are only changing the behavior in the place where we are using it.
Pat Maddox, can you give more detail? Why wouldn’t the new class be garbage collected when the nil_or goes out of scope? Why is defining it on the metaclass different?
Hi there…. Why can’t one simply do this?
class NilClass
def method_missing *dontcare
return nil
end
end
Hi,
I am quite new to ruby and I was looking for a solution that lets me call deep hierarchies on data-structures without having to check for nil.
The nil_or method does not seem to fill my needs becaus one couldnt call
x.nil_or.some_data.nil_or.other_data.nil_or....
so I tried my luck and came up with this
Which then could be called like
NilOr.new(my_object_where_i_dont_care_its_nil).x.y.z.to_s
Damn! Pastie didnt workout:
http://pastie.org/506875
This is why I love http://www.pgrs.net. Incredible posts.