Reputation: 5345
I'm trying to use the safe navigation operator &
to optionally call the []
method on a variable which may be a hash or nil
. As far as I know, that's what safe navigation operators are for.
my_hash = nil
puts "hello, #{ my_hash&[:test] }"
my_hash = { test: "world" }
puts "hello, #{ my_hash&[:test] }"
I expect this to output:
hello, false
hello, world
But but it does not work with hashes; instead, I get:
hello, false
undefined method `&` for `{:test=>"world"}:Hash` (NoMethodError)
What am I doing wrong?
Upvotes: 3
Views: 2932
Reputation: 84393
You've got logic and syntax errors in your program including:
""
).&.
).In the sections below, I address your logic errors and offer some suggestions for using native Hash methods that accomplish the results your looking for without silently swallowing errors that would have brought more attention to the problematic parts of the code.
The goal of this answer isn't just to point out errors, but to also demonstrate how to accomplish what you're trying to do successfully. Your mileage may vary.
There's are a couple of key logic error in your original question. If you want to return "false" for interpolation if a Hash value doesn't exist, use #fetch. For example:
hash = {}
hash.fetch :test, false
#=> false
However, there's an important caveat. Hash#fetch will raise a NoMethodError if called on nil, because nil doesn't respond to fetch:
nil.respond_to? :fetch
#=> false
This is the problem safe navigation is designed to handle: a missing method. It therefore makes sense to use safe navigation if you swallow the exception rather than rescue it or handle it with #method_missing. For example:
hash = nil
hash.fetch :test, false
#=> NoMethodError: undefined method `fetch' for nil:NilClass
hash&.fetch :test, false
#=> nil
However, doing this essentially results in silently swallowing an error in the same way that hash.fetch :test rescue false
would, so it's generally the wrong thing to do because it hides the fact that there's a logic problem or that you've gotten ahold of an unexpected object like nil rather than an empty hash. However, it would certainly solve your interpolation problem:
hash = nil
"#{hash.fetch :test rescue false}"
#=> "false"
For your posted code, I stress that you probably want to use sensible Hash methods that don't silently swallow exceptions, but your real-world mileage may certainly vary.
You appear to have at least four other related problems in your original question:
&.
so it only works with methods using dotted notation. my_hash&[:test]
is not a valid construct for that.Hash&.[]
to be a valid construct (which it doesn't), this wouldn't do what you're expecting anyway.nil.to_s
doesn't do what you think; it returns an empty string, not an instance of FalseClass. For proof, consider that "#{nil}" #=> ""
.Consider the following examples:
hash = {}
hash.fetch :test
#=> KeyError: key not found: :test
hash.fetch :test, nil
#=> nil
hash.values_at :test
#=> [nil]
Hash#fetch with a default of nil is probably what you want in most cases, although you can certainly rescue KeyError or rescue nil
(or even rescue false
) if you prefer to do that, although a catch-all rescue for truly unexpected exceptions is generally considered a code smell.
Upvotes: -3
Reputation: 168179
Your problem is that there is no such thing as "the safe navigation operator &
" as you assume.
What is called the safe navigation operator is &.
. Nowhere in your code:
my_hash&[:test]
is there a safe navigation operator &.
.
Upvotes: 0
Reputation: 28305
I'm trying to use
&
to optionally call the[]
method on a hash which may or may not benil
.
In order to do that, you need to directly invoke the []
method. hash[:key]
is actually calling the :[]
method on the hash:
# This will not work:
my_hash&[:test]
# This will:
my_hash&.[](:test)
This is clearly not the prettiest solution, though... You may instead want to use a guard clause in the method (return if my_hash.nil?
), or do something like this:
my_hash.to_h[:test]
Upvotes: 1
Reputation: 30071
Because this
my_hash[:test]
is syntactic sugar for this one
my_hash.[](:test)
so this should work
my_hash&.[](:test)
but it's not pretty, I know.
Upvotes: 8