Ruby: acceda al hash multidimensional y evite el acceso al objeto nil
conditional (3)
Posible duplicado:
Ruby: Nils en una declaración IF
¿Hay alguna manera clara de evitar llamar a un método en nil en un hash de parámetros anidado?
Digamos que trato de acceder a un hash como este:
my_hash[''key1''][''key2''][''key3'']
Esto es bueno si key1, key2 y key3 existen en el (los) hash (es), pero ¿y si, por ejemplo, key1 no existe?
Entonces obtendría NoMethodError: undefined method [] for nil:NilClass
. Y a nadie le gusta eso.
Hasta ahora me ocupo de esto haciendo un condicional como:
if my_hash[''key1''] && my_hash[''key1''][''key2'']
...
¿Es esto apropiado, hay alguna otra forma Rubiest de hacerlo?
Condiciones my_hash[''key1''] && my_hash[''key1''][''key2'']
no se siente DRY .
Alternativas:
1) magia de autovivification De esa publicación:
def autovivifying_hash
Hash.new {|ht,k| ht[k] = autovivifying_hash}
end
Luego, con tu ejemplo:
my_hash = autovivifying_hash
my_hash[''key1''][''key2''][''key3'']
Es similar al enfoque Hash.fetch en que ambos operan con nuevos valores hash como valores predeterminados, pero esto mueve los detalles al tiempo de creación. Es cierto que esto es un poco de trampa: nunca volverá a ser ''nil'' solo un hash vacío, que se crea sobre la marcha. Dependiendo de su caso de uso, esto podría ser un desperdicio.
2) Resuma la estructura de datos con su mecanismo de búsqueda y maneje el caso no encontrado detrás de las escenas. Un ejemplo simplista:
def lookup(model, key, *rest)
v = model[key]
if rest.empty?
v
else
v && lookup(v, *rest)
end
end
#####
lookup(my_hash, ''key1'', ''key2'', ''key3'')
=> nil or value
3) Si te sientes monádico puedes echarle un vistazo a esto, Maybe
Hay muchos enfoques para esto.
Si usa Ruby 2.3 o superior, puede usar dig
my_hash.dig(''key1'', ''key2'', ''key3'')
Mucha gente se adhiere al rubí simple y encadena las pruebas de &&
guard.
También podría usar stdlib Hash#fetch :
my_hash.fetch(''key1'', {}).fetch(''key2'', {}).fetch(''key3'', nil)
Algunos prefieren encadenar el método #try de #try .
my_hash.try(:[], ''key1'').try(:[], ''key2'').try(:[], ''key3'')
Otros usan andand
myhash[''key1''].andand[''key2''].andand[''key3'']
Algunas personas piensan que los nilos egocéntricos son una buena idea (aunque alguien podría perseguirte y torturarte si descubrieran que haces esto).
class NilClass
def method_missing(*args); nil; end
end
my_hash[''key1''][''key2''][''key3'']
Podría usar Enumerable#reduce (o alias inject).
[''key1'',''key2'',''key3''].reduce(my_hash) {|m,k| m && m[k] }
O quizás extienda Hash o simplemente su objeto hash de destino con un método de búsqueda anidado
module NestedHashLookup
def nest *keys
keys.reduce(self) {|m,k| m && m[k] }
end
end
my_hash.extend(NestedHashLookup)
my_hash.nest ''key1'', ''key2'', ''key3''
Ah, y ¿cómo podríamos olvidarnos de la mónada maybe ?
Maybe.new(my_hash)[''key1''][''key2''][''key3'']
También podría usar Object#andand .
my_hash[''key1''].andand[''key2''].andand[''key3'']