Ruby-on-rails 等效于 .try() 的哈希以避免在 nil 上出现“未定义方法”错误?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/6224875/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-03 01:18:25  来源:igfitidea点击:

Equivalent of .try() for a hash to avoid "undefined method" errors on nil?

ruby-on-railsrubyruby-on-rails-3hash

提问by sscirrus

In Rails we can do the following in case a value doesn't exist to avoid an error:

在 Rails 中,如果值不存在,我们可以执行以下操作以避免错误:

@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?

在上述情况下,session[:comments]try[@comment.id]不起作用。什么会?

回答by Andrew Grimm

You forgot to put a .before the try:

你忘了.在 之前放一个try

@myvar = session[:comments].try(:[], @comment.id)

since []is the name of the method when you do [@comment.id].

因为[]是您执行时方法的名称[@comment.id]

回答by baxang

The announcement of Ruby 2.3.0-preview1includes an introduction of Safe navigation operator.

Ruby 2.3.0-preview1 的发布包括引入安全导航操作符。

A safe navigation operator, which already exists in C#, Groovy, and Swift, is introduced to ease nil handling as obj&.foo. Array#digand Hash#digare also added.

引入了 C#、Groovy 和 Swift 中已经存在的安全导航运算符,以简化 nil 处理作为obj&.foo. Array#dig并且 Hash#dig还添加了。

This means as of 2.3 below code

这意味着从下面的代码 2.3

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:

但是,应该注意的&是,这不是替换#try. 看看这个例子:

> 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#digand Hash#dig. So now this

它还包括一种类似的方式:Array#digHash#dig。所以现在这个

city = params.fetch(:[], :country).try(:[], :state).try(:[], :city)

can be rewritten to

可以改写为

city = params.dig(:country, :state, :city)

Again, #digis 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 methodwill be raised.

同样,#dig不是复制#try的行为。所以要小心返回值。如果params[:country]返回,例如,一个整数,TypeError: Integer does not have #dig method将被引发。

回答by Arsen7

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:

最漂亮的解决方案是Mladen Jabnovi的旧答案?,因为它可以让您比使用直接.try()调用更深入地挖掘哈希,如果您希望代码仍然看起来不错:

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.

您应该小心处理各种对象(尤其是params),因为字符串和数组也会响应 :[],但返回的值可能不是您想要的,并且对于用作索引的字符串或符号,Array 会引发异常。

That is the reason why in the suggested formof this method (below) the (usually ugly)test for .is_a?(Hash)is used instead of (usually better).respond_to?(:[]):

这就是为什么在此方法的建议形式(下面)中使用(通常丑陋)测试.is_a?(Hash)代替(通常更好)的原因.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)".

最后一个例子会引发一个异常:“Symbol as array index (TypeError)”,如果它没有被这个丑陋的“is_a?(Hash)”保护。

回答by Pablo Castellazzi

The proper use of trywith a hash is @sesion.try(:[], :comments).

正确使用带有哈希的try@sesion.try(:[], :comments).

@session.try(:[], :comments).try(:[], commend.id).try(:[], 'temp_value')

回答by Benjamin Dobell

Update:As of Ruby 2.3 use #dig

更新:从 Ruby 2.3 开始使用#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).

大多数响应 [] 的对象都需要一个 Integer 参数,Hash 是一个例外,它可以接受任何对象(例如字符串或符号)。

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

以下是Arsen7 答案的稍微更健壮的版本,它支持嵌套数组、哈希以及任何其他期望传递给 [] 的整数的对象。

It's not fool proof, as someone may have created an object that implements [] and does notaccept 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):

这不是万无一失的,因为有人可能创建了一个实现 [] 并且接受 Integer 参数的对象。但是,此解决方案在常见情况下效果很好,例如从 JSON(同时具有 Hash 和 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.

它可以像 Arsen7 的解决方案一样使用,但也支持数组,例如

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

json.get_deep 'users', 1, 'name', 'first_name' # Pulls out 'Bob'

回答by sawa

@myvar = session.fetch(:comments, {}).fetch(@comment.id, {})["temp_value"]

From Ruby 2.0, you can do:

从 Ruby 2.0 开始,您可以执行以下操作:

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

From Ruby 2.3, you can do:

从 Ruby 2.3 开始,您可以执行以下操作:

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

回答by Rajesh Paul

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

说你想找,params[:user][:email]但不确定是否user在里面params。然后-

you can try:

你可以试试:

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

It will return either nil(if useris not there or emailis not there in user) or otherwise the value of emailin user.

这将返回nil(如果user不存在或email不存在user)或其他价值emailuser

回答by Steve Smith

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

从 Ruby 2.3 开始,这变得更容易了。try您现在可以使用Hash#dig文档),而不必嵌套语句或定义自己的方法。

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 trythan some of the examples above. If any of the arguments lead to the hash returning nil then it will respond nil.

这有一个额外的好处,那就是try比上面的一些例子更像。如果任何参数导致散列返回 nil,那么它将响应 nil。

回答by Nicolas Goy

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

回答by Max Williams

When you do this:

当你这样做时:

myhash[:one][:two][:three]

You're just chaining a bunch of calls to a "[]" method, an the error occurs if myhash[:one] returns nil, because nil doesn't have a [] method. So, one simple and rather hacky way is to add a [] method to Niclass, which returns nil: i would set this up in a rails app as follows:

您只是将一堆调用链接到“[]”方法,如果 myhash[:one] 返回 nil,则会发生错误,因为 nil 没有 [] 方法。因此,一种简单且相当hacky的方法是向Niclass添加一个[]方法,该方法返回nil:我将在rails应用程序中进行如下设置:

Add the method:

添加方法:

#in lib/ruby_extensions.rb
class NilClass
  def [](*args)
    nil
  end
end

Require the file:

要求文件:

#in config/initializers/app_environment.rb
require 'ruby_extensions'

Now you can call nested hashes without fear: i'm demonstrating in the console here:

现在,您可以毫无畏惧地调用嵌套哈希:我正在此处的控制台中进行演示:

>> hash = {:foo => "bar"}
=> {:foo=>"bar"}
>> hash[:foo]
=> "bar"
>> hash[:doo]
=> nil
>> hash[:doo][:too]
=> nil