Equivalent of .try() for a hash to avoid "undefined method" errors on nil? [duplicate]
In Rails we can do the following in case a value doesn't exist to avoid an error:
@myvar = @comment.try(:body)
What is the equivalent when I'm digging deep into a hash and don't want to get an error?
@myvar = session[:comments][@comment.id]["temp_value"]
# [:comments] may or may not exist here
In the above case, session[:comments]try[@comment.id]
doesn't work. What would?
You forgot to put a .
before the try
:
@myvar = session[:comments].try(:[], @comment.id)
since []
is the name of the method when you do [@comment.id]
.
The announcement of Ruby 2.3.0-preview1 includes an introduction of Safe navigation operator.
A safe navigation operator, which already exists in C#, Groovy, and Swift, is introduced to ease nil handling as
obj&.foo
.Array#dig
andHash#dig
are also added.
This means as of 2.3 below code
account.try(:owner).try(:address)
can be rewritten to
account&.owner&.address
However, one should be careful that &
is not a drop in replacement of #try
. Take a look at this example:
> params = nil
nil
> params&.country
nil
> params = OpenStruct.new(country: "Australia")
#<OpenStruct country="Australia">
> params&.country
"Australia"
> params&.country&.name
NoMethodError: undefined method `name' for "Australia":String
from (pry):38:in `<main>'
> params.try(:country).try(:name)
nil
It is also including a similar sort of way: Array#dig
and Hash#dig
. So now this
city = params.fetch(:[], :country).try(:[], :state).try(:[], :city)
can be rewritten to
city = params.dig(:country, :state, :city)
Again, #dig
is not replicating #try
's behaviour. So be careful with returning values. If params[:country]
returns, for example, an Integer, TypeError: Integer does not have #dig method
will be raised.