rails example create array ruby-on-rails ruby hash hash-of-hashes

ruby-on-rails - example - ruby array



¿Cómo evitar NoMethodError para elementos faltantes en hash anidados, sin repetidos controles nulos? (16)

Estoy buscando una buena manera de evitar la comprobación de nil en cada nivel en hashes profundamente anidados. Por ejemplo:

name = params[:company][:owner][:name] if params[:company] && params[:company][:owner] && params[:company][:owner][:name]

Esto requiere tres comprobaciones y crea un código muy feo. ¿Alguna forma de evitar esto?


¿Eres capaz de evitar usar un hash multidimensional y usar

params[[:company, :owner, :name]]

o

params[[:company, :owner, :name]] if params.has_key?([:company, :owner, :name])

¿en lugar?


(Aunque es una pregunta muy antigua, tal vez esta respuesta sea útil para algunas personas con como yo que no pensaron en la expresión de estructura de control "begin rescue").

Lo haría con una declaración de catch try (comenzar rescate en lenguaje ruby):

begin name = params[:company][:owner][:name] rescue #if it raises errors maybe: name = ''John Doe'' end


A partir de Ruby 2.3.0:

También puedes usar &. llamado "operador de navegación segura" como: params&.[](:company)&.[](:owner)&.[](:name) . Este es perfectamente seguro.

Usar dig no es seguro ya que params.dig(:company, :owner, :name) fallará si params es nil.

Sin embargo, puede combinar los dos como: params&.dig(:company, :owner, :name) .

Por lo tanto, cualquiera de los siguientes es seguro de usar:

params&.[](:company)&.[](:owner)&.[](:name)

params&.dig(:company, :owner, :name)


El mejor compromiso entre la funcionalidad y la claridad IMO es Randwald''s and and. Con eso, harías:

params[:company].andand[:owner].andand[:name]

Es similar a try , pero en este caso se lee mucho mejor, ya que todavía está enviando mensajes como normal, pero con un delimitador entre eso llama la atención sobre el hecho de que está tratando nils especialmente.


Equivalente a la segunda solución que sugirió el usuario mpd , solo Ruby más idiomática:

class Hash def deep_fetch *path path.inject(self){|acc, e| acc[e] if acc} end end hash = {a: {b: {c: 3, d: 4}}} p hash.deep_fetch :a, :b, :c #=> 3 p hash.deep_fetch :a, :b #=> {:c=>3, :d=>4} p hash.deep_fetch :a, :b, :e #=> nil p hash.deep_fetch :a, :b, :e, :f #=> nil



Escribe la fealdad una vez, luego ocúltala

def check_all_present(hash, keys) current_hash = hash keys.each do |key| return false unless current_hash[key] current_hash = current_hash[key] end true end


Hacer:

params.fetch(''company'', {}).fetch(''owner'', {})[''name'']

También en cada paso, puede usar un método apropiado construido en NilClass para escapar de nil , si fuera una matriz, cadena o numérico. Simplemente agregue to_hash al inventario de esta lista y to_hash .

class NilClass; def to_hash; {} end end params[''company''].to_hash[''owner''].to_hash[''name'']


No necesita acceder a la definición del hash original: puede anular el método [] sobre la marcha después de obtenerlo utilizando h.instance_eval, por ej.

h = {1 => ''one''} h.instance_eval %q{ alias :brackets :[] def [] key if self.has_key? key return self.brackets(key) else h = Hash.new h.default = {} return h end end }

Pero eso no te ayudará con el código que tienes, porque estás confiando en un valor no encontrado para devolver un valor falso (por ejemplo, nada) y si haces alguna de las cosas de auto-vivificación "normal" vinculadas a lo anterior, Va a terminar con un hash vacío para valores no encontrados, que se evalúa como "verdadero".

Podría hacer algo como esto: solo busca valores definidos y los devuelve. No puede configurarlos de esta manera, porque no tenemos forma de saber si la llamada está en el LHS de una tarea.

module AVHash def deep(*args) first = args.shift if args.size == 0 return self[first] else if self.has_key? first and self[first].is_a? Hash self[first].send(:extend, AVHash) return self[first].deep(*args) else return nil end end end end h = {1=>2, 3=>{4=>5, 6=>{7=>8}}} h.send(:extend, AVHash) h.deep(0) #=> nil h.deep(1) #=> 2 h.deep(3) #=> {4=>5, 6=>{7=>8}} h.deep(3,4) #=> 5 h.deep(3,10) #=> nil h.deep(3,6,7) #=> 8

De nuevo, sin embargo, solo puedes verificar valores con él, no asignarlos. Entonces no es una auto-vivificación real, como todos sabemos y nos encanta en Perl.


No sé si eso es lo que quieres, pero ¿quizás podrías hacer esto?

name = params[:company][:owner][:name] rescue nil


Peligroso pero funciona:

class Object def h_try(key) self[key] if self.respond_to?(''[]'') end end

Podemos hacer nuevas

user = { :first_name => ''My First Name'', :last_name => ''my Last Name'', :details => { :age => 3, :birthday => ''June 1, 2017'' } } user.h_try(:first_name) # ''My First Name'' user.h_try(:something) # nil user.h_try(:details).h_try(:age) # 3 user.h_try(:details).h_try(:nothing).h_try(:doesnt_exist) #nil

La cadena "h_try" sigue un estilo similar a una cadena "try".


Ruby 2.3.0 introdujo un nuevo método llamado dig en ambos Hash y Array que resuelve este problema por completo.

name = params.dig(:company, :owner, :name)

Devuelve nil si la clave falta en cualquier nivel.

Si está usando una versión de Ruby anterior a 2.3, puede usar la gema ruby_dig o implementarla usted mismo:

module RubyDig def dig(key, *rest) if value = (self[key] rescue nil) if rest.empty? value elsif value.respond_to?(:dig) value.dig(*rest) end end end end if RUBY_VERSION < ''2.3'' Array.send(:include, RubyDig) Hash.send(:include, RubyDig) end


Si es rieles, usa

params.try(:[], :company).try(:[], :owner).try(:[], :name)

Oh, espera, eso es incluso más feo. ;-)


Si quieres entrar en monkeypatching puedes hacer algo como esto

class NilClass def [](anything) nil end end

Entonces una llamada a params[:company][:owner][:name] arrojará nulo si en algún punto uno de los hashes anidados es nulo.

EDITAR: Si quieres una ruta más segura que también proporcione código limpio, podrías hacer algo como

class Hash def chain(*args) x = 0 current = self[args[x]] while current && x < args.size - 1 x += 1 current = current[args[x]] end current end end

El código se vería así: params.chain(:company, :owner, :name)


Yo escribiría esto como:

name = params[:company] && params[:company][:owner] && params[:company][:owner][:name]

No es tan limpio como el ? operador en Io , pero Ruby no tiene eso. La respuesta de @ThiagoSilveira también es buena, aunque será más lenta.


require ''xkeys'' # on rubygems.org params.extend XKeys::Hash # No problem that we got params from somebody else! name = params[:company, :owner, :name] # or maybe... name = params[:company, :owner, :name, :else => ''Unknown''] # Note: never any side effects for looking # But you can assign too... params[:company, :reviewed] = true