可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
This question already has answers here:
Closed 4 years ago.
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?
回答1:
Check Ick's maybe. You don't need to significantly refactor your code, just intersperse maybe proxies when necessary:
params[:subject].maybe[:name]
The same author (raganwald) also wrote andand, with the same idea.
回答2:
You can use #try
, but I don't think it's much better:
params[:subject].try(:[], :name)
Or use #fetch
with default parameter:
params.fetch(:subject, {}).fetch(:name, nil)
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)
回答3:
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
回答4:
params[:subject].try(:[], :name)
is the cleanest way
回答5:
When I have same problem in coding, I sometimes use `rescue'.
name = params[:subject][:name] rescue ""
# => ""
This is not good manners, but I think it is simple way.
EDIT: I don't use this way often anymore. I recommend try
or fetch
.
回答6:
Not really. You can try fetch
or try
(from ActiveSupport) but it's not much cleaner than what you already have.
More info here:
UPDATE: Forgot about andand
:
- http://andand.rubyforge.org/
andand
lets you do:
params[:user].andand[:name] # nil guard is built-in
Similarly, you can use maybe
from the Ick library per the answer above.
回答7:
Or, add []
to it.
class NilClass; def [](*); nil end end
params[:subject][:name]
回答8:
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
回答9:
I cross posted this from my answer over here:
How to check if params[:some][:field] is nil?
I have been looking for a better solution too.
So I figured let's use try
a different way to test for a nested key being set:
params[:some].try(:has_key?, :field)
It's not bad. You get nil
vs. false
if it's not set. You also get true
if the param is set to nil
.
回答10:
I wrote Dottie for just this use case — reaching deep into a hash without first knowing whether the entire expected tree exists. The syntax is more succinct than using try
(Rails) or maybe
(Ick). For example:
# 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
Check out the docs if you want to see more: https://github.com/nickpearson/dottie
回答11:
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
回答12:
You can avoid the double hash access with an inline assignment:
my_param = subj_params = params[:subject] && subj_params[:name]