ActiveSupport’s #try might not be doing what you think it’s doing

来源:互联网 时间:1970-01-01

Today I had reason to verify the exact semantics of ActiveSupport’s Object#tryextension for an upcoming RubyTapas episode. #tryis usually used to paper over nilvalues. Unfortunately, #trydoes more than this, and as a result it can easily hide defects.

Consider: we have a variable with a number in it. We want to round the number down.

num = 23.1num.floor # => 23

Something that sometimes happens in our applications is that we takein some numeric information in string form, and forget to convert itto a number before using it as one. This is a bug that we need to beaware of, and Ruby is more than happy to let us know with anexception.

num = "23.1"num.floor # =># ~> NoMethodError# ~> undefined method `floor' for "23.1":String# ~># ~> xmptmp-in49044q82.rb:2:in `'

Now let’s say we know that the value of numis sometimes nil. Rather than removing this nilincursion from our code, we decide to turn the nilcase into a harmless no-op with .try.

num = nilnum.try(:floor) # => nil

Remember the previous example, where we got a string and forgot to turn it into a number, and Ruby told us all about it? Guess what happens now:

num = "23.1"num.try(:floor) # => nil

Congratulations:wenow have a silent defect. Good luck finding it in the absence of a NoMethodError.

Why? Because #trydoesn’t actually care about nils. It’s only concerned with whether an object responds to the given message. This is consistent with the method’s name. In my experience, though, it is not consistent with what most programmers actually want when they use #try.

Incidentally, Ruby’s recently-added “safe navigation” operatordoes not share this drawback. It is strictly nil-sensitive.

(I’ll be covering this issue and a whole array of related techniques in an upcoming RubyTapasminiseries.)

UPDATE: A few people have pointed out that at least as of Rails 4.0, there is now a #try!variant with the safer semantics. At the time of writing, this version has yet to make an appearance in the official ActiveSupport Rails Guide.

If you’re using Rails 4 or later, this new version should probably be your default choice.