Where are they now? Revisiting Ruby 2.3 goodies
More than two years ago, Ruby 2.3 was released. Alongside some great performance improvements, it brought a couple of new things to the language that I actually have not seen used that often in the wild. Time to revisit and shine some spotlight on them!
Safe navigation operator
Safe navigation operator (or lonely operator, if you prefer) is a feature influenced by the likes of Swift and Groovy. It’s quite useful to avoid a pyramid of doom in long chained expressions. It’s best illustrated with an example:
country = @site.user.country
In the above, what happens is site has no user? Or there is no site? Kaboom! Ugly NoMethodError for nil class. There are ways to go about it. You could always do:
country = @site && @site.user && @site.user.country
But that’s just long and silly. If you use ActiveSupport there’s always use .try
method:
country = @site.try(:user).try(:country)
Ever so slightly better, but relies on another library. Also, .try
doesn’t check if the object responds to the method we try to use. In the example above, if user did not respond to .country
method we would still get back nil. There is a more restrictive version, .try!
which solves that issue, but we still end up with a bit of typing here.
&.
safe navigation operator introduced in Ruby 2.3, works pretty much like .try!
method, but it’s a part of the core language. In use, it looks like this:
country = @site&.user&.country
Nicer, cleaner and more succinct code. Pretty cool, eh? But as mentioned in the intro, I have not seen this used very often.
There are some good reasons for it, probably. If you’re doing lots of chained method calls, you are probably at odds with Law of Demeter. Programmers are also an opinionated bunch and they might disagree with simply returning nil object as a placeholder. To this effect, something like Maybe pattern might be preferred.
Still, if you are doing method chains in the first place, you might as well do them clean, right?
Hash#dig and Array#dig
Ruby 2.3 also introduced .dig
method available on both hashes and arrays. The use case here is similar to safely navigation operator: avoiding those pesky NoMethodErrors and pyramids of doom.
Previously, to avoid those, probably best bet was to use .fetch
method like:
hash = {foo: {bar: {baz: 1}}}
hash.fetch(:foo, {}).fetch(:bar, {}).fetch(:baz, nil)array = [1, [2, [3,4]]]
array.fetch(1, []).fetch(1, []).fetch(1, nil)
Works, but again, quite confusing and very ugly looking. Still, seen that quite a few times, especially when parsing responses from badly constructed APIs. When you don’t control the data, sometimes you just need to bite the bullet and work with what you’ve got.
.dig
to the rescue!
hash.dig(:foo, :bar, :baz)arr.dig(1, 1, 1)
The above will just return nil if any step (hash key or array index) ends up being nil. Short and sweet.
Should I use it?
Maybe? Probably? Depends?
Things like safe navigation operator definitely make for cleaner looking code, I mean there’s a clear winner between these two, right?
country = @site && @site.user && @site.user.country
=== vs ===
country = @site&.user&.country
It’s all useful when applied right. Still, with this pattern, it’s very possible that you end up writing sloppier code. Law of Demeter? Nah, who needs that? Methods returning what’s expected? Nah, just return a nil there and it all will be fine.
Or will it?
nil.nil? => true
nil&.nil? => nil
Oh well!