相当于. try ()的散列,以避免“未定义的方法”错误?

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

@myvar = @comment.try(:body)

当我深入挖掘一个散列并且不想得到一个错误时,等价的是什么?

@myvar = session[:comments][@comment.id]["temp_value"]
# [:comments] may or may not exist here

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

97105 次浏览

尝试使用

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

你忘了把 .放在 try之前:

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

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

正确使用带有散列的 试试看@sesion.try(:[], :comments)

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

最漂亮的解决方案是一个旧的 作者: 姆拉登 · 贾布拉诺维,因为它可以让你比直接使用 .try()调用更深入地挖掘散列,如果你想让代码看起来仍然漂亮:

class Hash
def get_deep(*fields)
fields.inject(self) {|acc,e| acc[e] if acc}
end
end

对于各种对象(特别是 params) ,您应该小心,因为 String 和 Array 也会响应: [] ,但返回的值可能不是您想要的,Array 会对用作索引的 String 或 Symbols 产生异常。

这就是为什么在这种方法的 建议的形式(下图)中使用 .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

最后一个例子 会引起一个例外: “符号作为数组索引(TypeError)”,如果它没有被这个丑陋的“ is _ a? (Hash)”保护的话。

当你这样做的时候:

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

您只是将一堆调用链接到一个“[]”方法,如果 myhash [ : one ]返回 nil,则会发生错误,因为 nil 没有[]方法。因此,一个简单而又相当古怪的方法是向 Niclass 添加一个[]方法,它返回 nil: 我会在一个 Railsapp 中设置如下:

添加方法:

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

需要文件:

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

现在您可以无所畏惧地调用嵌套哈希: 我在这里的控制台中演示:

>> hash = {:foo => "bar"}
=> {:foo=>"bar"}
>> hash[:foo]
=> "bar"
>> hash[:doo]
=> nil
>> hash[:doo][:too]
=> nil
@myvar = session.fetch(:comments, {}).fetch(@comment.id, {})["temp_value"]

在 Ruby 2.0中,您可以这样做:

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

从 Ruby 2.3开始,你可以这样做:

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

另一种方法是:

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

这也可能被认为是有点危险的,因为它可以隐藏太多,我个人喜欢它。

如果你想要更多的控制权,你可以考虑这样的事情:

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

更新: 对于 Ruby 2.3,使用 < a href = “ http://Ruby-doc.org/core-2.3.0 _ preview1/Hash.html # method-i-dig”rel = “ noReferrer”> # dig

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

下面是一个稍微健壮一些的 Arsen7的回答版本,它支持嵌套 Array、 Hash 以及任何其他需要传递 Integer 到[]的对象。

这不是傻瓜证据,因为有人可能已经创建了一个实现[]和 没有接受 Integer 参数的对象。但是,这个解决方案在一般情况下工作得很好,例如从 JSON 中提取嵌套值(它同时具有哈希和数组) :

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

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

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


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

当我最近再次尝试这个问题时,安德鲁的回答对我不起作用。也许有些事情已经改变了?

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

'[]'用引号代替符号 :[]

假设你想找到 params[:user][:email],但是不确定 user是否在 params中。然后-

你可以试试:

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

它将返回 nil(如果 user不存在或者 emailuser中不存在) ,或者返回 useremail的值。

在 Ruby 2.3中,这会变得容易一些。您现在可以使用 Hash#dig(文件) ,而不必嵌套 try语句或定义自己的方法。

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


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

或者在上面的例子中:

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

与上面的一些示例相比,这样做的另一个好处是更像 try。如果任何参数导致散列返回 nil,那么它将响应 nil。

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

一个安全的导航操作符,它已经存在于 C # 、 Groovy 和 快速,是为了简化零处理作为 obj&.foo.Array#dig和 还添加了 Hash#dig

这意味着从代码下面的2.3开始

account.try(:owner).try(:address)

可以被改写成

account&.owner&.address

然而,我们应该注意的是,&并不是 #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

它也包括一种类似的方式: Array#digHash#dig

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

可以被改写成

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

同样,#dig没有复制 #try的行为。所以小心返回值。例如,如果 params[:country]返回一个 Integer,则会引发 TypeError: Integer does not have #dig method