等价的.try()用于散列以避免在nil上出现"未定义的方法"错误?

ssc*_*rus 164 ruby hash ruby-on-rails ruby-on-rails-3

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

@myvar = @comment.try(:body)
Run Code Online (Sandbox Code Playgroud)

当我深入挖掘哈希并且不想得到错误时,等价物是什么?

@myvar = session[:comments][@comment.id]["temp_value"] 
# [:comments] may or may not exist here
Run Code Online (Sandbox Code Playgroud)

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

And*_*imm 263

你忘了.在之前放一个try:

@myvar = session[:comments].try(:[], @comment.id)
Run Code Online (Sandbox Code Playgroud)

既然[]是当你的方法的名称[@comment.id].

  • 如果找不到密钥,则fetch会抛出错误,除非您传递了默认值.所以你需要写:session [:comments] .try(:fetch,@ comment.id,nil) (21认同)
  • 由于`:[]`在`try`中看起来有点奇怪,你也可以把它写成`session [:comments] .try(:fetch,@ comment.id)`. (14认同)
  • 从Ruby 2.3开始,不再需要`try`.@baxang有最好的答案,如下. (5认同)

bax*_*ang 61

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

一个安全的导航操作符,已经存在于C#,Groovy和Swift中,用于简化nil处理obj&.foo.Array#dig并且 Hash#dig还添加了.

这意味着截至2.3下面的代码

account.try(:owner).try(:address)
Run Code Online (Sandbox Code Playgroud)

可以改写成

account&.owner&.address
Run Code Online (Sandbox Code Playgroud)

但是,一个人应该小心,&不要替换掉#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
Run Code Online (Sandbox Code Playgroud)

它也包括类似的方式:Array#digHash#dig.所以现在这个

city = params.fetch(:[], :country).try(:[], :state).try(:[], :city)
Run Code Online (Sandbox Code Playgroud)

可以改写成

city = params.dig(:country, :state, :city)
Run Code Online (Sandbox Code Playgroud)

再一次,#dig不是复制#try的行为.所以要小心返回值.如果params[:country]返回(例如,Integer)TypeError: Integer does not have #dig method将被引发.

  • 对于那些想知道的人来说,如果散列是"nil"则会中断 (10认同)
  • &.**不会破坏`nil`(与`.dig`不同).因此安全的实现是:`params&.dig(:country,:state,:city)` (4认同)
  • 请注意,这实际上不适用于Rails会话,因为ActionDispatch :: Request :: Session未实现#dig (2认同)
  • 如果`params [:country]`不是`hash`或`nil`(例如``string`),也会中断 (2认同)

Ars*_*en7 25

最美丽的解决方案是MladenJablanović的旧答案,因为它可以让你使用直接.try()调用深入挖掘哈希值,如果你想让代码看起来还不错:

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

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

这就是为什么在这个方法的建议形式(下面)中使用(通常是丑陋的)测试.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
Run Code Online (Sandbox Code Playgroud)

最后一个例子会引发一个异常:"符号作为数组索引(TypeError)"如果没有被这个丑陋的"is_a?(Hash)"守护.


Pab*_*zzi 15

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

@session.try(:[], :comments).try(:[], commend.id).try(:[], 'temp_value')
Run Code Online (Sandbox Code Playgroud)

  • "不能嵌套"是错误的.但是对于你的特殊情况,我的欣赏是正确的.你需要做的是使用try with:[],直接使用它,你需要使用fetch. (2认同)

Ben*_*ell 14

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

大多数响应[]的对象都期望一个Integer参数,Hash是一个接受任何对象(例如字符串或符号)的异常.

以下是Arsen7的答案的一个稍微强大的版本,它支持嵌套的Array,Hash,以及期望Integer传递给[]的任何其他对象.

这不是万无一失的,因为有人可能创建了一个实现[]并且接受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
Run Code Online (Sandbox Code Playgroud)

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

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

json.get_deep 'users', 1, 'name', 'first_name' # Pulls out 'Bob'
Run Code Online (Sandbox Code Playgroud)


saw*_*awa 13

@myvar = session.fetch(:comments, {}).fetch(@comment.id, {})["temp_value"]
Run Code Online (Sandbox Code Playgroud)

从Ruby 2.0开始,您可以:

@myvar = session[:comments].to_h[@comment.id].to_h["temp_value"]
Run Code Online (Sandbox Code Playgroud)

从Ruby 2.3,您可以:

@myvar = session.dig(:comments, @comment.id, "temp_value")
Run Code Online (Sandbox Code Playgroud)

  • 看起来session没有实现dig:undefined方法`dig'for#<ActionDispatch :: Request :: Session:0x007ffc6cafa698> (4认同)

Raj*_*aul 11

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

你可以试试:

params[:user].try(:[], :email)
Run Code Online (Sandbox Code Playgroud)

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


Ste*_*ith 11

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

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

h.dig(:foo, :bar, :baz)           #=> 1
h.dig(:foo, :zot)                 #=> nil
Run Code Online (Sandbox Code Playgroud)

或者在上面的例子中:

session.dig(:comments, @comment.id, "temp_value")
Run Code Online (Sandbox Code Playgroud)

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


Nic*_*Goy 6

另一种方法:

@myvar = session[:comments][@comment.id]["temp_value"] rescue nil
Run Code Online (Sandbox Code Playgroud)

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

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

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
Run Code Online (Sandbox Code Playgroud)