¿Hay alguna manera clara de evitar llamar a un método en nil en un hash de parámetros nested?

Estoy interesado en obtener el parámetro “nombre” nested de hash de params. Llamando algo así como

params[:subject][:name] 

arroja un error cuando params [: subject] está vacío. Para evitar este error, suelo escribir algo como esto:

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

¿Hay una manera más limpia de implementar esto?

Compruebe Ick es tal vez . No es necesario refactorizar significativamente su código, simplemente intercalar los proxies cuando sea necesario:

 params[:subject].maybe[:name] 

El mismo autor ( raganwald ) también escribió andand , con la misma idea.

  1. Puedes usar #try , pero no creo que sea mucho mejor:

     params[:subject].try(:[], :name) 
  2. O usa #fetch con el parámetro predeterminado:

     params.fetch(:subject, {}).fetch(:name, nil) 
  3. O puede configurar #default= a hash vacío nuevo, pero luego no intente modificar los valores devueltos por esto:

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

    También rompe todas las pruebas simples de existencia, por lo que no puedes escribir:

     if params[:subject] 

    porque devolverá hash vacío, ahora tienes que agregar #present? llamar a cada prueba.

    También esto siempre devuelve hash cuando no hay ningún valor para la clave, incluso cuando espera una cadena.

Pero por lo que veo, intentas extraer el parámetro nested, en lugar de asignarlo al modelo y ubicar tu lógica. Si tiene un modelo de Subject , simplemente asigne:

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

shuld extraerá todos los parámetros que el usuario haya rellenado. Luego intente guardarlos para ver si el usuario pasó valores válidos.

Si está preocupado por acceder a los campos que el usuario no debe configurar, agregue la lista blanca attr_accessible para los campos a los que se les debe permitir asignarlos en masa (como en mi ejemplo, con @subject.attributes = params[:subject] para la actualización)

Ruby 2.3.0 hace que esto sea muy fácil de hacer con #dig

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

params[:subject].try(:[], :name) es la forma más limpia

Cuando tengo el mismo problema en la encoding, a veces uso `rescue ‘.

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

Esto no es buenos modales, pero creo que es una manera simple.

EDITAR: Ya no uso esta manera a menudo. Recomiendo try o fetch .

Realmente no. Puedes intentar fetch o try (desde ActiveSupport) pero no es mucho más limpio que lo que ya tienes.

Más información aquí:

  • Hash nested definido? ()

ACTUALIZACIÓN: se olvidó de andand :

andand te deja hacer:

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

Del mismo modo, puede usar maybe de la biblioteca Ick por la respuesta anterior .

O bien, agregue [] a ella.

 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 

p.ej

 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 

Lo crucé por mi respuesta aquí:

¿Cómo verificar si params [: some] [: field] es nil?

He estado buscando una mejor solución también.

Así que pensé que vamos a usar una forma diferente de probar una clave anidada que se está configurando:

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

No está mal. Obtienes nil vs. false si no está configurado. También se true si el parámetro está establecido en nil .

Escribí a Dottie solo para este caso de uso: profundizar en un hash sin saber primero si existe todo el árbol esperado. La syntax es más sucinta que usar try (Rails) o maybe (Ick). Por ejemplo:

 # 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 

Consulte los documentos si desea ver más: https://github.com/nickpearson/dottie

Solía:

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

da:

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

Puede evitar el doble acceso hash con una asignación en línea:

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