等效于 .try() 的哈希以避免 nil 上的“未定义方法”错误? [复制]

Posted

技术标签:

【中文标题】等效于 .try() 的哈希以避免 nil 上的“未定义方法”错误? [复制]【英文标题】:Equivalent of .try() for a hash to avoid "undefined method" errors on nil? [duplicate] 【发布时间】:2011-09-07 16:01:10 【问题描述】:

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

@myvar = @comment.try(:body)

当我深入挖掘哈希并且不想出错时,什么是等效的?

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

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

【问题讨论】:

相关问题:***.com/questions/4371716/… Ruby 2.3 引入了Hash#dig,这使得try 在此处变得不必要。 @baxang 现在有最好的答案。 Dig 不会使 try 变得不必要,因为它在除哈希之外的其他对象上仍然失败。例如无。但是将 dig 与 save 运算符结合使用 => session&.dig(:cmets, @comment.id, "temp_value") 【参考方案1】:

您忘记在try 之前添加.

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

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

【讨论】:

由于:[]try 中看起来有点奇怪,你也可以写成session[:comments].try(:fetch, @comment.id) fetch 如果找不到密钥,则会引发错误,除非您传递默认值。所以你需要写: session[:cmets].try(:fetch, @comment.id, nil) try 从 Ruby 2.3 开始不再需要。 @baxang 在下面给出了最佳答案。 如果为零怎么办?我想返回 [],而不是 nil。 抱歉,我是 ruby​​ 新手,这里的 :[] 是什么?【参考方案2】:

The announcement of Ruby 2.3.0-preview1 包含安全导航运算符的介绍。

一个安全的导航操作符,已经存在于 C#、Groovy 和 Swift 被引入以简化 nil 处理为obj&.fooArray#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] 返回一个整数,则会引发TypeError: Integer does not have #dig method

【讨论】:

如果哈希是 nil 则中断 请注意,这实际上不适用于 Rails 会话,因为 ActionDispatch::Request::Session 没有实现#dig 如果params[:country] 不是hashnil(例如string)也会中断 &。 会在nil 上中断(与.dig 本身不同)。因此一个安全的实现是:params&amp;.dig(:country, :state, :city)【参考方案3】:

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

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

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

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

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

【讨论】:

实际上,由于nil 不是Hash,您可能可以简化为fields.inject(self) |acc,e| acc[e] if acc.is_a?(Hash) 但我感觉#respond_to 会更好。 @riffraff:您对acc &amp; acc.is_a?() 的看法完全正确——认为这是一个错误;-)。但是respond_to 不行,因为String 和很多其他对象也响应:[],但是这个方法的结果不是这里想要的。 使用object.try的重点是object可以是nil。而在您的情况下,nil.get_deep 将引发异常。你的解决方案没有回答这个问题。 问题是“当我深入挖掘哈希时”,我不认为session 可能是nil,但如果可以,那就完全没问题了致电session.try(:get_deep, :comments, @comment.id, "temp_value")【参考方案4】:

try 与哈希的正确用法是@sesion.try(:[], :comments)

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

【讨论】:

-1 为什么不能嵌套? try 适用于任何Object,而nilObject,所以我怀疑以下方法会起作用:nil.try(:do).try(:do_not).try(:there_is_a_try) “不能嵌套”是错误的。但是对于您的特殊情况,我的赞赏是正确的。您需要做的是使用try with :[],直接使用它需要使用fetch。【参考方案5】:

更新:从 Ruby 2.3 开始,使用 #dig

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

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

这不是万无一失的,因为有人可能创建了一个实现 [] 的对象,而 接受 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

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

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

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

【讨论】:

【参考方案6】:

从 Ruby 2.3 开始,这变得容易一些。您现在可以使用 Hash#dig (documentation),而不必嵌套 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。

【讨论】:

不能在会话哈希上使用 dig 最佳答案,可以正常使用常规哈希【参考方案7】:
@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")

【讨论】:

因为.try 不是这样做的。 看起来会话没有实现 dig: #<:request::session:0x007ffc6cafa698> 的未定义方法 `dig'【参考方案8】:

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

你可以试试:

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

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

【讨论】:

【参考方案9】:

另一种方法:

@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

【讨论】:

【参考方案10】:

当你这样做时:

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

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

添加方法:

#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

【讨论】:

这太棒了——谢谢 Max!你知道这有什么缺点吗?其他人对此有看法吗? 它将隐藏代码其他部分中出现意外 nil 的问题。我认为这种方法很危险。【参考方案11】:

当我最近再次尝试时,安德鲁的回答对我不起作用。也许有些事情发生了变化?

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

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

【讨论】:

【参考方案12】:

尝试使用

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

【讨论】:

如果我不知道[:comments][@comment.id]是否存在呢? 在这种情况下,我认为最好创建嵌套的 IF 语句来检查会话中的每个参数 @sscirrus:你可以做session[:comments][@comment.id]["temp_value"] if (session[:comments] and session[:comments][@comment.id]) @AndrewGrimm - 是的,我认为这会起作用,但我希望有更简洁的东西(我会在一个地方有一些类似的表达式,它看起来很重代码)。我喜欢你的实际答案。 :)

以上是关于等效于 .try() 的哈希以避免 nil 上的“未定义方法”错误? [复制]的主要内容,如果未能解决你的问题,请参考以下文章

Ruby - 访问多维哈希并避免访问 nil 对象

之间的 PROC SQL 上的哈希连接等效项

等效于 Linux 上的 GetTickCount()

等效于 Linux 上的 Win32 性能计数器 [重复]

给定一个类型对象的实例,如何调用该类的构造函数(在其他语言中等效于GetConstructor)

qmake 等效于 cmake execute_process()