Is there a clean way to avoid calling a method on

2020-01-24 09:41发布

I'm interested in getting the nested 'name' parameter of a params hash. Calling something like

params[:subject][:name]

throws an error when params[:subject] is empty. To avoid this error I usually write something like this:

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

Is there a cleaner way to implement this?

12条回答
仙女界的扛把子
2楼-- · 2020-01-24 09:57
class Hash
  def fetch2(*keys)
    keys.inject(self) do |hash, key|
      hash.fetch(key, Hash.new)
    end
  end
end

e.g.

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
查看更多
趁早两清
3楼-- · 2020-01-24 09:58
  1. You can use #try, but I don't think it's much better:

    params[:subject].try(:[], :name)
    
  2. Or use #fetch with default parameter:

    params.fetch(:subject, {}).fetch(:name, nil)
    
  3. Or you can set #default= to new empty hash, but then don't try to modify values returned from this:

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

    It also breaks all simple tests for existence, so you can't write:

    if params[:subject]
    

    because it will return empty hash, now you have to add #present? call to every test.

    Also this always returns hash when there is no value for key, even when you expect string.

But from what I see, you try to extract nested parameter, instead of assigning it to model and there placing your logic. If you have Subject model, then simply assigning:

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

shuld extract all your parameters user filled in form. Then you try to save them, to see if user passed valid values.

If you're worrying about accessing fields which user should not set, then add attr_accessible whitelist for fields whoich should be allowed to set with mass assignment (as in my example, of with @subject.attributes = params[:subject] for update)

查看更多
家丑人穷心不美
4楼-- · 2020-01-24 10:02

params[:subject].try(:[], :name) is the cleanest way

查看更多
欢心
5楼-- · 2020-01-24 10:03

I used:

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])

gives:

=> "Jack"
=> "use internet"
=> nil
查看更多
看我几分像从前
6楼-- · 2020-01-24 10:05

Ruby 2.3.0 makes this very easy to do with #dig

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

h.dig(:foo, :bar, :baz)           #=> 1
h.dig(:foo, :zot, :baz)           #=> nil
查看更多
走好不送
7楼-- · 2020-01-24 10:06

Or, add [] to it.

class NilClass; def [](*); nil end end
params[:subject][:name]
查看更多
登录 后发表回答