Equivalent of .try() for a hash to avoid "undefined method" errors on nil? [duplicate]
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 = nilnil> params&.countrynil> params = OpenStruct.new(country: "Australia")#<OpenStruct country="Australia">> params&.country"Australia"> params&.country&.nameNoMethodError: undefined method `name' for "Australia":Stringfrom (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.
The most beautiful solution is an old answer by Mladen Jablanović, as it lets you to dig in the hash deeper than you could with using direct .try()
calls, if you want the code still look nice:
class Hash def get_deep(*fields) fields.inject(self) {|acc,e| acc[e] if acc} endend
You should be careful with various objects (especially params
), because Strings and Arrays also respond to :[], but the returned value may not be what you want, and Array raises exception for Strings or Symbols used as indexes.
That is the reason why in the suggested form of this method (below) the (usually ugly) test for .is_a?(Hash)
is used instead of (usually better) .respond_to?(:[])
:
class Hash def get_deep(*fields) fields.inject(self) {|acc,e| acc[e] if acc.is_a?(Hash)} endenda_hash = {:one => {:two => {:three => "asd"}, :arr => [1,2,3]}}puts a_hash.get_deep(:one, :two ).inspect # => {:three=>"asd"}puts a_hash.get_deep(:one, :two, :three ).inspect # => "asd"puts a_hash.get_deep(:one, :two, :three, :four).inspect # => nilputs a_hash.get_deep(:one, :arr ).inspect # => [1,2,3]puts a_hash.get_deep(:one, :arr, :too_deep ).inspect # => nil
The last example would raise an exception: "Symbol as array index (TypeError)" if it was not guarded by this ugly "is_a?(Hash)".