The Trials and Tribulations of `try` in Ruby on Rails

We’re going to be covering Object#try, which is provided by Active Support’s core extensions. We’ll talk about what’s changed from Rails 3 through 5, and some intricacies to be aware of when using it.

Rails 3.2 and introduction to trying

For reference: here’s the source.

We’ll be continuing with the dinosaur motif from last week’s post, so let’s assume that we have this class defined:

class Dinosaur
  def roar
    "ROOAAARR"
  end
end

In Rails 3.2, the beginning of time as far as we’re concerned, Active Support adds a try method to Object and to NilClass.

The effect is explained succinctly in the source:

+try+ behaves like +Object#send+, unless called on +NilClass+.

If you call try on nil, it will just return nil.

Thus, for example, you have an instance variable @dinosaur that could have been set to nil, but you’d like it to roar otherwise, you can call @dinosaur.try(:roar) without worrying about a NoMethodError. If you don’t have a dinosaur, it will just return nil.

Note that if you call Dinosaur.new.try(:dance), you will get a NoMethodError. You have a Dinosaur instance, but it does not respond to the dance method. This will change as we move forward in Rails versions.

Rails 4 and the introduction of try!

For reference: here’s the source.

In Rails 4, the behavior of try is changed. No longer will calling Dinosaur.new.try(:dance) give you a NoMethodError: instead, it will return nil. The other behavior is unchanged, meaning that Dinosaur.new.try(:roar) will still result in "ROOAAARR" and nil.try(:roar) will still result in nil.

If, however, you’re not a fan of this behavior and want to still be given the reasonable NoMethodError when you typo and ask your dinosaur to raor, Active Support introduces the try! method. Its behavior matches that of Rails 3.

Rails 5 and fun with Delegator

Again, for reference, here’s the source.

You may have noticed that in Rails 3 and 4, only Object and NilClass are monkey-patched to add try. This works as expected most of the time, but the exception is when classes inherit from BasicObject instead of Object: they don’t have try.

This usually comes up when using SimpleDelegator instances (which we’re fond of using for presenters via the frosting gem). The call to try will be delegated to the wrapped object instead of being called on the instance of the delegator itself.

This has been used by some as a hack to intentionally bypass the presenter for calling a method on the wrapped object, but it usually causes confusion when you’re expecting the method you’re trying to call to be called on the presenter instance.

With Rails 5, try will now be included in Delegator (the parent of SimpleDelegator), which will fix this unexpected behavior (and break things for anyone who’s been using it as a hack to call methods on the wrapped object).

So try is pretty cool

But it can certainly bite you in the ass if you don’t understand the intricacies of its implementation.

Have a relevant story to tell? Or would you just like to show me pictures of funny hats? Either way, I’m on Twitter as @jon_evans, and I would love to hear from you.

Tweet at Jon

Share this post!