Object#try is great for this case:
1
2
3
4
5
|
person = Person.find(:first)
puts person && person.age
# or: puts person.age if person
# or: puts person.age unless person.nil?
# or whatever you use! |
It’s even greater when the variable/method names are long:
1
2
3
|
contractor_or_employee.attributes &&
contractor_or_employee.attributes.address &&
contractor_or_employee.attributes.address.country |
becomes:
|
contractor_or_employee.try(:attributes).try(:address).try(:country) |
Basically, it’s helpful any time you want to call a method on an object but aren’t sure if that object is nil or not, and you don’t want it to raise a NoMethodError if the object is nil; instead, you’d like it to return nil.
I have been aware of Object#try for some time now, particularly since this post, and the update (to accept more arguments) here , and then our own version, which accepts both arguments and a block, as follows:
1
2
3
4
5
|
class Object
def try(method_name, *args, &block)
respond_to?(method_name) ? send(method_name, *args, &block) : nil
end
end |
All these are great; however, there is an improvement. We now use nil_or for most of our needs. (Note that this is a different nil_or than is talked about here more on that below.) If your use case is strictly that you don’t know whether an object will be nil or not, the nil_or presented here is better, and here’s why:
Firstly, it doesn’t have the overhead of respond_to?, which isn’t trivial if you use this all over your code. Secondly, try could get you into problems in a few edge cases: for example, say you are trying to see if an object is nil and you use try but accidentally use the wrong method name. try will return nil without alerting you to your mistake, whereas this one will still call the method on the object and raise a NoMethodError.
The principle here is that, with try, there is a disjoint between the need (checking if an object is nil or not) and the behavior (checking respond_to? on an object), which leads to inefficiency as well as unpredictability. nil_or bridges that gap, so here it is:
1
2
3
4
5
|
class Object
def nil_or(method_name, *args, &block)
self.nil? ? nil : send(method_name, *args, &block)
end
end |
Use it just as you would try, so for example:
|
contractor_or_employee.nil_or(:attributes).nil_or(:address).nil_or(:country) |
Yes, I know there is another Object#nil_or out there, here and (amazingly) at its own domain here, and the advantage to that one is that you can easily string along method calls like so:
|
contractor_or_employee.attributes.address.country |
without the hassle of parameters. However, if you take a look at the code you’ll see that it’s pretty inefficient, instantiating a new class each time it hits a nil. We can’t afford that inefficiency, but if you can, it might be a better option for you.