style rails guide code ruby hash coding-style

rails - Ruby Style: cómo verificar si existe un elemento hash anidado



ruby rubocop (14)

Aquí hay una manera de hacer un chequeo profundo de cualquier valor falso en el hash y cualquier hash anidado sin parche de monos en la clase Ruby Hash (POR FAVOR, no pongas parches en las clases de Ruby, es algo que no debes hacer, NUNCA) .

(Asumiendo Rails, aunque podrías modificar esto fácilmente para trabajar fuera de Rails)

def deep_all_present?(hash) fail ArgumentError, ''deep_all_present? only accepts Hashes'' unless hash.is_a? Hash hash.each do |key, value| return false if key.blank? || value.blank? return deep_all_present?(value) if value.is_a? Hash end true end

Considere una "persona" almacenada en un hash. Dos ejemplos son:

fred = {:person => {:name => "Fred", :spouse => "Wilma", :children => {:child => {:name => "Pebbles"}}}} slate = {:person => {:name => "Mr. Slate", :spouse => "Mrs. Slate"}}

Si la "persona" no tiene hijos, el elemento "secundarios" no está presente. Entonces, para el Sr. Slate, podemos verificar si tiene padres:

slate_has_children = !slate[:person][:children].nil?

Entonces, ¿qué pasa si no sabemos que "pizarra" es una "persona" hash? Considerar:

dino = {:pet => {:name => "Dino"}}

No podemos verificar fácilmente para los niños más:

dino_has_children = !dino[:person][:children].nil? NoMethodError: undefined method `[]'' for nil:NilClass

Entonces, ¿cómo verificaría la estructura de un hash, especialmente si está anidado profundamente (incluso más profundo que los ejemplos proporcionados aquí)? Tal vez una mejor pregunta es: ¿cuál es la "forma de Ruby" para hacer esto?



Dado

x = {:a => {:b => ''c''}} y = {}

puedes verificar xey de esta manera:

(x[:a] || {})[:b] # ''c'' (y[:a] || {})[:b] # nil


La forma más obvia de hacerlo es simplemente verificar cada paso del camino:

has_children = slate[:person] && slate[:person][:children]

Uso de .nil? en realidad solo se requiere cuando usa falso como valor de marcador de posición, y en la práctica esto es raro. En general, puedes simplemente probar que existe.

Actualización : si está usando Ruby 2.3 o posterior, hay un método de dig integrado que hace lo que se describe en esta respuesta.

De lo contrario, también puede definir su propio método Hash "dig" que puede simplificar esto de manera sustancial:

class Hash def dig(*path) path.inject(self) do |location, key| location.respond_to?(:keys) ? location[key] : nil end end end

Este método comprobará cada paso del camino y evitará tropezar con llamadas a cero. Para las estructuras poco profundas, la utilidad es algo limitada, pero para las estructuras profundamente anidadas, me parece invaluable:

has_children = slate.dig(:person, :children)

También puede hacer esto más robusto, por ejemplo, probando si la entrada: children está realmente poblada:

children = slate.dig(:person, :children) has_children = children && !children.empty?


Otra alternativa:

dino.fetch(:person, {})[:children]


Puedes intentar jugar con

dino.default = {}

O por ejemplo:

empty_hash = {} empty_hash.default = empty_hash dino.default = empty_hash

De esa forma puedes llamar

empty_hash[:a][:b][:c][:d][:e] # and so on... dino[:person][:children] # at worst it returns {}


Puedes usar el andand gem:

require ''andand'' fred[:person].andand[:children].nil? #=> false dino[:person].andand[:children].nil? #=> true

Puede encontrar más explicaciones en http://andand.rubyforge.org/ .


Simplificando las respuestas anteriores aquí:

Cree un método Recursive Hash cuyo valor no puede ser nil, como el siguiente.

def recursive_hash Hash.new {|key, value| key[value] = recursive_hash} end > slate = recursive_hash > slate[:person][:name] = "Mr. Slate" > slate[:person][:spouse] = "Mrs. Slate" > slate => {:person=>{:name=>"Mr. Slate", :spouse=>"Mrs. Slate"}} slate[:person][:state][:city] => {}

Si no te importa crear hashes vacíos si no existe :)


También puede definir un módulo para aliar los métodos de paréntesis y usar la sintaxis de Ruby para leer / escribir elementos anidados.

ACTUALIZACIÓN: en lugar de anular los accesos al soporte, solicite la instancia de Hash para extender el módulo.

module Nesty def []=(*keys,value) key = keys.pop if keys.empty? super(key, value) else if self[*keys].is_a? Hash self[*keys][key] = value else self[*keys] = { key => value} end end end def [](*keys) self.dig(*keys) end end class Hash def nesty self.extend Nesty self end end

Entonces puedes hacer:

irb> a = {}.nesty => {} irb> a[:a, :b, :c] = "value" => "value" irb> a => {:a=>{:b=>{:c=>"value"}}} irb> a[:a,:b,:c] => "value" irb> a[:a,:b] => {:c=>"value"} irb> a[:a,:d] = "another value" => "another value" irb> a => {:a=>{:b=>{:c=>"value"}, :d=>"another value"}}


Thks @tadman por la respuesta.

Para aquellos que quieren perfs (y están atrapados con ruby ​​<2.3), este método es 2.5 veces más rápido:

unless Hash.method_defined? :dig class Hash def dig(*path) val, index, len = self, 0, path.length index += 1 while(index < len && val = val[path[index]]) val end end end

y si usas RubyInline , este método es 16 veces más rápido:

unless Hash.method_defined? :dig require ''inline'' class Hash inline do |builder| builder.c_raw '' VALUE dig(int argc, VALUE *argv, VALUE self) { rb_check_arity(argc, 1, UNLIMITED_ARGUMENTS); self = rb_hash_aref(self, *argv); if (NIL_P(self) || !--argc) return self; ++argv; return dig(argc, argv, self); }'' end end end


Tradicionalmente, realmente tenías que hacer algo como esto:

structure[:a] && structure[:a][:b]

Sin embargo, Ruby 2.3 agregó una característica que hace de esta manera más elegante:

structure.dig :a, :b # nil if it misses anywhere along the way

Hay una gema llamada ruby_dig que hará un parche de respaldo para usted.


Uno podría usar hash con el valor predeterminado de {} - hash vacío. Por ejemplo,

dino = Hash.new({}) dino[:pet] = {:name => "Dino"} dino_has_children = !dino[:person][:children].nil? #=> false

Eso también funciona con Hash ya creado:

dino = {:pet=>{:name=>"Dino"}} dino.default = {} dino_has_children = !dino[:person][:children].nil? #=> false

O puede definir el método [] para la clase nula

class NilClass def [](* args) nil end end nil[:a] #=> nil


def flatten_hash(hash) hash.each_with_object({}) do |(k, v), h| if v.is_a? Hash flatten_hash(v).map do |h_k, h_v| h["#{k}_#{h_k}"] = h_v end else h[k] = v end end end irb(main):012:0> fred = {:person => {:name => "Fred", :spouse => "Wilma", :children => {:child => {:name => "Pebbles"}}}} => {:person=>{:name=>"Fred", :spouse=>"Wilma", :children=>{:child=>{:name=>"Pebbles"}}}} irb(main):013:0> slate = {:person => {:name => "Mr. Slate", :spouse => "Mrs. Slate"}} => {:person=>{:name=>"Mr. Slate", :spouse=>"Mrs. Slate"}} irb(main):014:0> flatten_hash(fred).keys.any? { |k| k.include?("children") } => true irb(main):015:0> flatten_hash(slate).keys.any? { |k| k.include?("children") } => false

Esto aplanará todos los hashes en uno y luego en alguno? devuelve verdadero si existe alguna clave que coincida con la subcadena "hijos". Esto también podría ayudar.


dino_has_children = !dino.fetch(person, {})[:children].nil?

Tenga en cuenta que en los rieles también puede hacer:

dino_has_children = !dino[person].try(:[], :children).nil? #