在嵌套的参数哈希中,有没有一种优雅的方式可以避免在nil上调用方法?

33

我希望获取params哈希中嵌套的 'name' 参数。类似这样调用:

params[:subject][:name]

当params[:subject]为空时会抛出一个错误。为了避免这种错误,我通常会这样写:

if params[:subject] && params[:subject][:name]

有更简洁的实现方式吗?


2
几天前是同样的问题 :) https://dev59.com/qVXTa4cB1Zd3GeqP13fC#5394240 - fl00r
12个回答

20

检查Ickmaybe函数。无需重构代码,只需在必要时使用maybe代理:

params[:subject].maybe[:name]

同一位作者 (raganwald) 还用相同的想法写了一个叫 andand 的东西。


很有趣,我以前没听说过Ick。 - bowsersenior
既然RubyForge已经消失了,上面提供的Ick's maybe插件链接已经失效。新的gems网站rubygems.org有一个名为maybe的gem在这里: https://rubygems.org/gems/maybe。这是与RubyForge上的Ick's相同的吗? - Ryan Grow
@steel在下面给出了最佳答案:使用Ruby 2.3.0中引入的内置功能dig - user513951

17
  1. 你可以使用#try,但我认为它并没有更好:

params[:subject].try(:[], :name)
  • 或者使用具有默认参数的#fetch

    params.fetch(:subject, {}).fetch(:name, nil)
    
    或者您可以将#default=设置为新的空哈希表,但是随后不要尝试修改从中返回的值。
    params.default = {}
    params[:subject][:name]
    

    它也会破坏所有简单存在性测试,因此您无法编写以下内容:

    if params[:subject]
    
    因为它会返回空哈希,现在你必须在每个测试中添加#present?调用。
    此外,即使您期望字符串,当没有键值时,这总是返回哈希表。但从我看到的情况来看,您试图提取嵌套参数,而不是将其分配给模型并在那里放置逻辑。如果您有Subject模型,那么只需分配:
    @subject = Subject.new(params[:subject])
    

    应该提取用户在表单中填写的所有参数。接着,您尝试保存这些参数,以查看用户是否传递了有效值。

    如果你担心访问用户不应该设置的字段,那么添加attr_accessible白名单来允许使用批量分配设置的字段(就像我示例代码中的@subject.attributes = params[:subject]更新操作一样)。


  • 9

    使用#dig方法,Ruby 2.3.0使得这个过程非常简单。

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

    1
    如果 hnil,则 h.dig 将失败。考虑改用安全导航操作符或与 .dig 结合使用,如:h&.dig(:foo, :bar, :baz)h&.foo&.bar&.baz - thisismydesign
    我之前评论中关于哈希表的安全导航操作符的语法是错误的。正确的语法应该是:h&.[](:foo)&.[](:bar)&.[](:baz) - thisismydesign

    5
    params[:subject].try(:[], :name) 是最简洁的方式,用于访问名为 "name" 的 subject 参数。

    但是如果主题哈希中没有 :name 字段,则会抛出异常。您必须向 #fetch 添加默认值。 - MBO
    啊,抱歉我是指 params[:subject].try(:[], :name),我同意这并不比基本的冗长方式更好。 - lebreeze
    在Railscasts中,我认为Ryan使用try而没有:[](自那以后我也一直使用它)。这将使解决方案更加紧凑。;) - Pierre
    []是纯Ruby。它表示[]类型方法的符号形式。 - Jack Kinsella

    4

    当我在编码时遇到同样的问题,有时会使用`rescue'。


    (说明:`rescue'是ruby中的关键字,用于处理异常)
    name = params[:subject][:name] rescue ""
    # => ""
    

    这不是好习惯,但我认为这是一种简单的方法。

    编辑:我不再经常使用这种方法。 我建议使用tryfetch


    2
    并不是。你可以尝试使用fetchtry(来自ActiveSupport),但这并没有比你已经有的代码更简洁。

    更多信息在这里:

    更新:忘记了andand "

    andand 让你可以做到:

    "
    params[:user].andand[:name] # nil guard is built-in
    

    同样地,您可以根据上面的回答从Ick库中使用maybe

    2

    或者,给它加上[]

    class NilClass; def [](*); nil end end
    params[:subject][:name]
    

    1
    这太棒了。我删除了我的代码,因为这个比我的好多了。而且,感谢你展示了不带名称的扩展操作符*的用法。我之前不知道,从你那里学到了。 - sawa
    1
    有更多的人可以评论这个吗?我觉得这很棒! - Ben Wheeler

    1
    class Hash
      def fetch2(*keys)
        keys.inject(self) do |hash, key|
          hash.fetch(key, Hash.new)
        end
      end
    end
    

    例如

    require 'minitest/autorun'
    
    describe Hash do
      it "#fetch2" do
        { yo: :lo }.fetch2(:yo).must_equal :lo
        { yo: { lo: :mo } }.fetch2(:yo, :lo).must_equal :mo
      end
    end
    

    1
    我从这里的答案中转载了这个问题:

    如何检查params[:some][:field]是否为空?

    我也一直在寻找更好的解决方案。

    所以我想,让我们使用try来测试嵌套键是否被设置,采用不同的方式:

    params[:some].try(:has_key?, :field)
    

    这不错。如果没有设置,你会得到nil而不是false。如果参数设置为nil,你也会得到true


    0

    我使用了:

    params = {:subject => {:name => "Jack", :actions => {:peaceful => "use internet"}}}
    
    def extract_params(params, param_chain)
      param_chain.inject(params){|r,e| r=((r.class.ancestors.include?(Hash)) ? r[e] : nil)}
    end
    
    extract_params(params, [:subject,:name])
    extract_params(params, [:subject,:actions,:peaceful])
    extract_params(params, [:subject,:actions,:foo,:bar,:baz,:qux])
    

    给出:

    => "Jack"
    => "use internet"
    => nil
    

    网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接