【问题标题】:Is there a clean way to avoid calling a method on nil in a nested params hash? [duplicate]有没有一种干净的方法可以避免在嵌套的参数哈希中调用 nil 上的方法? [复制]
【发布时间】:2011-07-22 17:38:57
【问题描述】:

我对获取 params 哈希的嵌套“名称”参数感兴趣。调用类似的东西

params[:subject][:name]

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

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

有没有更简洁的方法来实现这个?

【问题讨论】:

标签: ruby-on-rails ruby hashmap null


【解决方案1】:

Ruby 2.3.0 让 #dig 可以轻松实现这一点

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

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

【讨论】:

  • 如果hnilh.dig 将失败。考虑改用安全导航运算符或与.dig 结合使用:h&.dig(:foo, :bar, :baz)h&.foo&.bar&.baz
  • 我之前的评论中安全导航运算符的哈希值语法不正确。正确的语法是:h&.[](:foo)&.[](:bar)&.[](:baz)
【解决方案2】:

检查Ick也许。您不需要大量重构代码,只需在必要时散布可能代理:

params[:subject].maybe[:name]

同一作者(raganwald)也写了andand,想法相同。

【讨论】:

  • 有趣,我以前没听说过 Ick。
  • 现在 ruby​​forge 消失了,上面指向 Ick 的可能插件的链接已损坏。新的 gems 站点 ruby​​gems.org 有一个 gem,可能在这里:rubygems.org/gems/maybe。这和来自 ruby​​ forge 的 Ick 是同一个吗?
  • @steel 的最佳答案如下:使用 Ruby 2.3.0 中引入的名为 dig 的内置功能。
【解决方案3】:

您可以通过内联赋值避免双重哈希访问:

my_param = subj_params = params[:subject] && subj_params[:name]

【讨论】:

    【解决方案4】:

    我在这里的回答中交叉发布了这个:

    How to check if params[:some][:field] is nil?

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

    所以我想让我们使用 try 一种不同的方式来测试设置的嵌套键:

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

    还不错。如果未设置,您将得到 nilfalse。如果参数设置为nil,您还将获得true

    【讨论】:

      【解决方案5】:
      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
      

      【讨论】:

        【解决方案6】:

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

        name = params[:subject][:name] rescue ""
        # => ""
        

        这不是礼貌,但我认为这是简单的方式。

        编辑:我不再经常使用这种方式了。我推荐tryfetch

        【讨论】:

          【解决方案7】:

          我用过:

          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
          

          【讨论】:

            【解决方案8】:

            我为这个用例写了Dottie——在不知道整个预期树是否存在的情况下深入到哈希中。语法比使用try (Rails) 或maybe (Ick) 更简洁。例如:

            # in a Rails request, assuming `params` contains:
            { 'person' => { 'email' => 'jon@example.com' } } # there is no 'subject'
            
            # standard hash access (symbols will work here
            # because params is a HashWithIndifferentAccess)
            params[:person][:email] # => 'jon@example.com'
            params[:subject][:name] # undefined method `[]' for nil:NilClass
            
            # with Dottie
            Dottie(params)['person.email'] # => 'jon@example.com'
            Dottie(params)['subject.name'] # => nil
            
            # with Dottie's optional class extensions loaded, this is even easier
            dp = params.dottie
            dp['person.email'] # => 'jon@example.com'
            dp['subject.name'] # => nil
            dp['some.other.deeply.nested.key'] # => nil
            

            如果您想了解更多信息,请查看文档:https://github.com/nickpearson/dottie

            【讨论】:

              【解决方案9】:
              1. 你可以用#try,但我觉得也好不了多少:

                params[:subject].try(:[], :name)
                
              2. 或者使用#fetch带默认参数:

                params.fetch(:subject, {}).fetch(:name, nil)
                
              3. 或者您可以将#default= 设置为新的空哈希,但不要尝试修改由此返回的值:

                params.default = {}
                params[:subject][:name]
                

                它也打破了所有简单的存在测试,所以你不能写:

                if params[:subject]
                

                因为它会返回空哈希,现在您必须在每个测试中添加#present? 调用。

                此外,当键没有值时,这总是返回哈希,即使您期望字符串也是如此。

              但据我所见,您尝试提取嵌套参数,而不是将其分配给模型并在那里放置您的逻辑。如果您有Subject 模型,那么只需分配:

              @subject = Subject.new(params[:subject])
              

              应该提取用户填写的所有参数。然后你尝试保存它们,看看用户是否传递了有效的值。

              如果您担心访问用户不应该设置的字段,请为应该允许设置的字段添加 attr_accessible 白名单并进行批量分配(如在我的示例中,使用 @subject.attributes = params[:subject] 进行更新)

              【讨论】:

                【解决方案10】:

                或者,添加[]

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

                【讨论】:

                • 这很棒。我删除了我的,因为这个比我的好得多。并且,感谢您展示了使用不带名称的 splat 运算符 *。我不知道,从你那里学来的。
                • 可以让更多人评论吗?这对我来说似乎很棒!
                【解决方案11】:

                不是真的。您可以尝试 fetchtry(来自 ActiveSupport),但它并不比您已有的干净。

                更多信息在这里:

                更新:忘记andand

                andand 让你这样做:

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

                同样,您可以使用maybe 中的Ick library per the answer above

                【讨论】:

                  【解决方案12】:

                  params[:subject].try(:[], :name)最干净的方式

                  【讨论】:

                  • 但是如果主题哈希中没有 :name 字段,则会引发异常。您必须将默认值添加到 #fetch
                  • 啊抱歉我的意思是params[:subject].try(:[], :name),我同意你的观点,不一定比基本的详细方式更好。
                  • 在 railscasts 中,我认为 Ryan 在没有 :[] 的情况下使用 try(从那时起我就一直在使用它)。这将使该解决方案更加紧凑;)
                  • :[] 是纯红宝石。这就是您将 [] 类型方法表示为符号的方式。
                  猜你喜欢
                  • 2014-01-02
                  • 2014-01-25
                  • 2021-02-18
                  • 2020-02-26
                  • 1970-01-01
                  • 2014-02-28
                  • 2018-06-30
                  • 1970-01-01
                  • 2015-03-08
                  相关资源
                  最近更新 更多