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

时间:2011-06-03 08:44:48

标签: ruby-on-rails ruby ruby-on-rails-3 hash

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

@myvar = @comment.try(:body)

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

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

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

12 个答案:

答案 0 :(得分:253)

您忘记在.之前添加try

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

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

答案 1 :(得分:60)

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

答案 2 :(得分:25)

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

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

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

这就是为什么在此方法的建议形式(下面)中使用.is_a?(Hash)(通常是丑陋的)测试代替(通常更好)<​​/ em> .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)”守卫。

答案 3 :(得分:14)

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

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

答案 4 :(得分:14)

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

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

以下是Arsen7's answer稍微更健壮的版本,它支持嵌套的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

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

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

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

答案 5 :(得分:12)

@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")

答案 6 :(得分:11)

从Ruby 2.3开始,这变得容易一些。您现在可以使用trydocumentation),而无需嵌套Hash#dig语句或定义自己的方法。

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。

答案 7 :(得分:10)

说您要查找params[:user][:email],但不确定user中是否有params。然后 -

你可以尝试:

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

它将返回 nil (如果user不存在或email中没有user)或其他值email中的user

答案 8 :(得分:6)

另一种方法:

@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

答案 9 :(得分:2)

执行此操作时:

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

你只是将一堆调用链接到“[]”方法,如果myhash [:one]返回nil,则会发生错误,因为nil没有[]方法。所以,一个简单且相当hacky的方法是向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

答案 10 :(得分:1)

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

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

'[]'是引号而不是符号:[]

答案 11 :(得分:-1)

尝试使用

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