等效的.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]不起作用。 什么会?

你忘了放一个.try之前:

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

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

Ruby 2.3.0-preview1的公告包括安全导航操作员的介绍。

已经存在于C#,Groovy和Swift中的安全导航运算符被引入,以减轻nil处理,如obj&.fooArray#digHash#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

最漂亮的解决scheme是MladenJablanović的一个旧的答案 ,因为如果你想让代码看起来不错的话 ,它可以让你深入挖掘哈希值,而不是直接使用.try()调用。

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

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

这就是为什么在这个方法的build议forms (下面)中使用.is_a?(Hash)(通常是丑陋的)testing而不是(通常更好的) .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 

最后一个例子会引发一个exception :“符号作为数组索引(TypeError)”,如果它不被这个丑陋的“is_a?(Hash)”所保护的话。

尝试与散列正确使用是@sesion.try(:[], :comments)

 @session.try(:[], :comments).try(:[], commend.id).try(:[], 'temp_value') 
 @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") 

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

大多数响应[]的对象需要一个Integer参数,而Hash是一个接受任何对象(如string或符号)的exception。

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

这不是一个愚蠢的certificate,因为某人可能已经创build了一个实现了[]的对象,并且接受一个I​​nteger参数。 但是,这种解决scheme在一般情况下效果很好,例如从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的解决scheme,但也支持arrays如

 json = { 'users' => [ { 'name' => { 'first_name' => 'Frank'} }, { 'name' => { 'first_name' => 'Bob' } } ] } json.get_deep 'users', 1, 'name', 'first_name' # Pulls out 'Bob' 

说你想findparams[:user][:email]但不确定user是否在params或不。 然后-

你可以试试:

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

它将返回nil (如果user不在那里或email是不存在的user )或否则在useremail的价值。

另一种方法:

 @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开始,这变得容易一些。 不必嵌套try语句或定义自己的方法,现在可以使用Hash#dig ( documentation )。

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

或者在上面的例子中:

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

这样做比上面的一些例子更像是try 。 如果任何参数导致散列返回零,那么它将回应为零。

当你这样做:

 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 

最近我再次尝试这个时,Andrew的答案对我来说并不适用。 也许有什么改变?

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

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

尝试使用

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