C’è un modo pulito per evitare di chiamare un metodo su nil in un hash di params annidato?

Sono interessato a ottenere il parametro ‘nome’ nidificato di un hash params. Chiamando qualcosa come

params[:subject][:name] 

genera un errore quando params [: subject] è vuoto. Per evitare questo errore, di solito scrivo qualcosa del genere:

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

C’è un modo più pulito per implementare questo?

Forse è il caso di Ick . Non è necessario ridigitare in modo significativo il codice, basta interspassare forse i proxy quando necessario:

 params[:subject].maybe[:name] 

Lo stesso autore ( raganwald ) scrisse anche e, con la stessa idea.

  1. Puoi usare #try , ma non penso che sia molto meglio:

     params[:subject].try(:[], :name) 
  2. Oppure usa #fetch con il parametro predefinito:

     params.fetch(:subject, {}).fetch(:name, nil) 
  3. Oppure puoi impostare #default= con un nuovo hash vuoto, ma poi non provare a modificare i valori restituiti da questo:

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

    Rompe anche tutti i test semplici per l’esistenza, quindi non puoi scrivere:

     if params[:subject] 

    perché restituirà l’hash vuoto, ora devi aggiungere #present? chiama ad ogni test.

    Inoltre, restituisce sempre hash quando non c’è alcun valore per la chiave, anche quando ci si aspetta una stringa.

Ma da quello che vedo, si tenta di estrarre il parametro nidificato, invece di assegnarlo al modello e di mettere la propria logica. Se hai il modello Subject , allora semplicemente assegna:

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

shuld estrae tutti i parametri compilati dall’utente. Quindi provi a salvarli, per vedere se l’utente ha passato valori validi.

Se ti preoccupi dell’accesso ai campi che l’utente non deve impostare, aggiungi la attr_accessible bianca attr_accessible per i campi a cui dovrebbe essere consentito impostare l’assegnazione di massa (come nel mio esempio, con @subject.attributes = params[:subject] per l’aggiornamento)

Ruby 2.3.0 rende questo molto facile da fare con #dig

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

params[:subject].try(:[], :name) è il modo più pulito

Quando ho lo stesso problema nella codifica, a volte uso `rescue ‘.

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

Non sono buone maniere, ma penso che sia un modo semplice.

EDIT: non uso più spesso in questo modo. Consiglio di try o fetch .

Non proprio. Puoi provare a fetch o try (da ActiveSupport) ma non è molto più pulito di quello che hai già.

Maggiori informazioni qui:

  • Hash annidato? ()

AGGIORNAMENTO: dimenticati di andand :

andand ti permette di fare:

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

Allo stesso modo, puoi usare maybe dalla libreria Ick per la risposta sopra .

Oppure aggiungi [] ad esso.

 class NilClass; def [](*); nil end end params[:subject][:name] 
 class Hash def fetch2(*keys) keys.inject(self) do |hash, key| hash.fetch(key, Hash.new) end end end 

per esempio

 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 

Ho incrociato questo post dalla mia risposta qui:

Come verificare se params [: some] [: field] è nullo?

Ho cercato anche una soluzione migliore.

Quindi ho pensato di try un modo diverso per testare l’impostazione di una chiave annidata:

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

Non è male. Ottieni nil vs. false se non è impostato. Si ottiene anche true se il parametro è impostato su nil .

Ho scritto Dottie solo per questo caso d’uso, raggiungendo in profondità un hash senza prima sapere se esiste l’intero albero atteso. La syntax è più sintetica rispetto all’utilizzo di try (Rails) o maybe (Ick). Per esempio:

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

Guarda i documenti se vuoi vedere di più: https://github.com/nickpearson/dottie

Ero solito:

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

dà:

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

Puoi evitare il doppio accesso ad hash con un incarico in linea:

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