102

In Rails, you can do hash.try(:[], :key) which helps if hash is potentially nil. Is there an equivalent version for using the new Ruby 2.3 safe navigation operator &. with []?

Jacob Murphy
  • 1,387
  • 2
  • 10
  • 18
  • Possible duplicate of [Ruby - Access multidimensional hash and avoid access nil object](https://stackoverflow.com/questions/10130726/ruby-access-multidimensional-hash-and-avoid-access-nil-object) – kolen Jun 07 '17 at 16:01

5 Answers5

128

&. is not equivalent to Rails' try, but you can use &. for hashes. Just use it, nothing special.

hash[:key1]&.[](:key2)&.[](:key3)

Although I would not do that.

sawa
  • 165,429
  • 45
  • 277
  • 381
92

Ruby 2.3 and later

There's Hash#dig method now that does just that:

Retrieves the value object corresponding to the each key objects repeatedly.

h = { foo: {bar: {baz: 1}}}

h.dig(:foo, :bar, :baz)           #=> 1
h.dig(:foo, :zot)                 #=> nil

http://ruby-doc.org/core-2.3.0_preview1/Hash.html#method-i-dig

Pre Ruby 2.3

I usually had something like this put into my intializer:

Class Hash
    def deep_fetch *args
      x = self
      args.each do |arg|
        x = x[arg]
        return nil if x.nil?
      end
      x
    end
end

and then

response.deep_fetch 'PaReqCreationResponse', 'ThreeDSecureVERes', 'Message', 'VERes', 'CH', 'enrolled'

in one wacky case.

The general consensus in the community seems to be to avoid both try and the lonely operator &.

iGEL
  • 16,540
  • 11
  • 60
  • 74
bbozo
  • 7,075
  • 3
  • 30
  • 56
  • 2
    Looks suspiciously similar in use cases to `dig`, introduced in 2.3 just as `&.`. Any differences? – D-side Mar 10 '16 at 19:35
  • 1
    It is better to place your extensions to `Hash` in a module and `prepend` it instead of doing a direct monkeypatch. – David S. May 12 '17 at 18:31
  • @DavidS. can you elaborate? – bbozo May 15 '17 at 08:46
  • Placing your extensions in a module allows you to test them in isolation from the actual class you're patching, and then actually patching it via `Module#prepend` will give you the ability to invoke `super` from your monkeypatch to call the method that you are overriding. – David S. May 15 '17 at 13:58
  • @DavidS. ah yes, I agree in principle, but in this case there's no change to existing behavior, it's not the use case prepend was created for (think alias_method_chain). Also, prepend only exists since Ruby 2.0 and the first example is there to provide a solution for earlier ruby versions. – bbozo May 15 '17 at 14:06
  • 5
    but `h.dig(:foo, :bar, :baz, :bing)` will raise an error – John Bachir Mar 24 '20 at 16:58
  • @JohnBachir you write this as you'd expect `dig` to swallow the fact that `1` is not `nil` or a hash. I say, the current behavior of `dig` is exactly what I expect. (And `1&.[](:bing)` will not work either). – iGEL Mar 09 '21 at 10:54
15

While hash&.[](:key) is elegant to the trained rubyist, I'd just use hash && hash[:key] as it reads better and more intuitively for the programmer coming after me, who may not be as familiar with the intricacies of ruby. Some extra characters in the codebase can sometimes save a whole lot of googling for someone else.

(Given the context you want to use this in is in a conditional statement, of course.)

Magne
  • 16,401
  • 10
  • 68
  • 88
  • 2
    ...especially as this syntax (`&.[](:key)`) is anything but well searchable! `dig` on the other hand can be identified as a method by the way it is used; and it's therefore relatively easy to get further infos on it by searching _ruby method dig_, e.g. – einjohn Feb 27 '20 at 17:05
14

A rather more legible way to use the safe navigation operator than using hash&.[](:slug) is to use the fetch method:

hash&.fetch(:slug)

If your key may not be defined, you can use the second argument as a default:

hash&.fetch(:slug, nil)
Sunny
  • 5,825
  • 2
  • 31
  • 41
  • This may be appropriate in some scenarios, but it's worth nothing that #[] and #fetch are not the same method, and fetch will throw an exception if the key does not exist in the hash. – ZoFreX Oct 03 '20 at 09:46
  • 2
    @ZoFreX: That's why I included `fetch(…, nil)` in the answer, which is exactly the same as `#[]`. – Sunny Oct 08 '20 at 19:07
11

Accepted answer will not account for when hash is nil...

You can rewrite what you have using the safe nav operator before the .try and that will work

hash&.try(:[], :key)

but you can also use:

http://ruby-doc.org/core-2.3.0_preview1/Hash.html#method-i-dig

A way you could do this on a hash is by doing...

hash&.dig(:key1, :key2 ...)

which will return nil if any key fetch isn't present.

{ key1: { key2: 'info' } } 

would return 'info'

{ key1: { wrong_key: 'info' } } 

would return nil

bf34
  • 284
  • 1
  • 7