Is there a clean way to avoid calling the method on nil in the hash nested parameter?

I am interested in getting the nested params hash 'name' parameter. Call something like

params[:subject][:name] 

throws an error when the [: subject] parameters are 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?

+33
hashmap null ruby ruby-on-rails
Mar 25 2018-11-11T00:
source share
12 answers

Check Ick . You do not need to significantly reorganize your code, just rephrase, perhaps a proxy, when necessary:

 params[:subject].maybe[:name] 

The same author ( raganwald ) also wrote andand , with the same idea.

+19
Mar 25 '11 at 8:01
source share
  • You can use #try , but I don't think this is much better:

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

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

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

    It also breaks up all simple existence tests, so you cannot write:

     if params[:subject] 

    because it will return an empty hash, now you need to add a #present? call #present? for each test.

    It also always returns a hash when there is no value for the key, even if you expect a string.

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

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

shuld retrieves all of your parameters populated by the user. Then you try to save them to check if the user has passed valid values.

If you are worried about accessing fields that the user should not set, add an attr_accessible whitelist for whoich fields that should be allowed to set with mass assignment (as in my example, with @subject.attributes = params[:subject] for updating)

+17
Mar 25 2018-11-11T00:
source share

Ruby 2.3.0 makes it very easy to do with # dig

 h = {foo: {bar: {baz: 1}}} h.dig(:foo, :bar, :baz) #=> 1 h.dig(:foo, :zot, :baz) #=> nil 
+8
Dec 31 '15 at 19:24
source share

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

+6
Mar 25 2018-11-11T00:
source share

When I have the same problem when coding, I sometimes use `rescue '.

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

These are not good manners, but I think this is an easy way.

EDIT: I no longer use this method. I recommend try or fetch .

+4
Mar 29 '11 at 2:12
source share

Not really. You can try fetch or try (from ActiveSupport), but it's not much cleaner than you already have.

More details here:

  • Nested hash defined? ()

UPDATE: Forgot andand :

andand allows andand to:

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

Similarly, you can use maybe from the Ick library for the answer above .

+2
Mar 25 2018-11-11T00:
source share

Or add to it [] .

 class NilClass; def [](*); nil end end params[:subject][:name] 
+2
Nov 02 '11 at 23:16
source share
 class Hash def fetch2(*keys) keys.inject(self) do |hash, key| hash.fetch(key, Hash.new) end end end 

eg.

 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
May 26 '15 at 4:00
source share

I crossed with this answer here:

How to check if the parameters are [: some] [: field] nil?

I was looking for the best solution.

So, I decided to let try use another way to check for a nested key:

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

It's not bad. You will get nil vs. false if it is not installed. You also get true if the parameter is nil .

+1
Nov 27 '15 at 4:51 on
source share

I wrote Dottie just for this use case - comes to the hash, not knowing if all the expected tree exists. The syntax is shorter 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 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

0
May 08 '14 at 20:20
source share

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 
0
09 Oct '14 at 12:23
source share

You can avoid double hash access with the built-in destination:

 my_param = subj_params = params[:subject] && subj_params[:name] 
0
Dec 22 '15 at 12:28
source share



All Articles