等效于 .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&.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#dig
和 Hash#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]
不是hash
或nil
(例如string
)也会中断
&。 不会在nil
上中断(与.dig
本身不同)。因此一个安全的实现是:params&.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 & 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
,而nil
是Object
,所以我怀疑以下方法会起作用: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
不存在或email
在user
中不存在)或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 上的“未定义方法”错误? [复制]的主要内容,如果未能解决你的问题,请参考以下文章