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
Equivalent of .try() for a hash to avoid "undefined method" errors on nil?
提问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#digandHash#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#dig和Hash#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
回答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)或其他价值email的user。
回答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

