Equivalent of .try() for a hash to avoid “undefine

2019-01-12 17:42发布

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?

12条回答
聊天终结者
2楼-- · 2019-01-12 18:18

say you want to find params[:user][:email] but it's not sure whether user is there in params or not. Then-

you can try:

params[:user].try(:[], :email)

It will return either nil(if user is not there or email is not there in user) or otherwise the value of email in user.

查看更多
老娘就宠你
3楼-- · 2019-01-12 18:20

Update: As of Ruby 2.3 use #dig

Most objects that respond to [] expect an Integer argument, with Hash being an exception that will accept any object (such as strings or symbols).

The following is a slightly more robust version of Arsen7's answer that supports nested Array, Hash, as well as any other objects that expect an Integer passed to [].

It's not fool proof, as someone may have created an object that implements [] and does not accept an Integer argument. However, this solution works great in the common case e.g. pulling nested values from JSON (which has both Hash and Array):

class Hash
  def get_deep(*fields)
    fields.inject(self) { |acc, e| acc[e] if acc.is_a?(Hash) || (e.is_a?(Integer) && acc.respond_to?(:[])) }
  end
end

It can be used the same as Arsen7's solution but also supports arrays e.g.

json = { 'users' => [ { 'name' => { 'first_name' => 'Frank'} }, { 'name' => { 'first_name' => 'Bob' } } ] }

json.get_deep 'users', 1, 'name', 'first_name' # Pulls out 'Bob'
查看更多
淡お忘
4楼-- · 2019-01-12 18:22

Try to use

@myvar = session[:comments][@comment.id]["temp_value"] if session[:comments]
查看更多
相关推荐>>
5楼-- · 2019-01-12 18:29

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}
  end
end

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)}
  end
end

a_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 # => nil
puts 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)".

查看更多
相关推荐>>
6楼-- · 2019-01-12 18:35

As of Ruby 2.3 this gets a little easier. Instead of having to nest try statements or define your own method you can now use Hash#dig (documentation).

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

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

Or in the example above:

session.dig(:comments, @comment.id, "temp_value")

This has the added benefit of being more like try than some of the examples above. If any of the arguments lead to the hash returning nil then it will respond nil.

查看更多
闹够了就滚
7楼-- · 2019-01-12 18:35

Another approach:

@myvar = session[:comments][@comment.id]["temp_value"] rescue nil

This might also be consider a bit dangerous because it can hide too much, personally I like it.

If you want more control, you may consider something like:

def handle # just an example name, use what speaks to you
    raise $! unless $!.kind_of? NoMethodError # Do whatever checks or 
                                              # reporting you want
end
# then you may use
@myvar = session[:comments][@comment.id]["temp_value"] rescue handle
查看更多
登录 后发表回答