Ruby-Accede al hash multidimensional y evita el acceso al objeto nil [duplicar]


Posible Duplicado:
Ruby: Nils en una sentencia IF
¿Hay una forma limpia de evitar llamar a un método en nil en un hash params anidado?

Digamos que intento acceder a un hash como este:

my_hash['key1']['key2']['key3']

Esto es bueno si key1, key2 y key3 existen en el hash(es), pero ¿qué pasa 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 de hacerlo?

Author: Community, 2012-04-12

3 answers

Hay muchos enfoques para esto.

Si usas Ruby 2.3 o superior, puedes usar dig

my_hash.dig('key1', 'key2', 'key3')

Muchas personas se adhieren a rubí simple y encadenan las pruebas de guardia &&.

También puedes usar stdlib Hash # fetch :

my_hash.fetch('key1', {}).fetch('key2', {}).fetch('key3', nil)

A algunos les gusta encadenar el método #try de ActiveSupport.

my_hash.try(:[], 'key1').try(:[], 'key2').try(:[], 'key3')

Otros usan y

myhash['key1'].andand['key2'].andand['key3']

Algunas personas piensan que nils egocéntricos son una buena idea (aunque alguien podría cazar usted abajo y la tortura si se encuentran que hacer 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 tal vez extender 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'

Oh, y ¿cómo podríamos olvidar la tal vez mónada?

Maybe.new(my_hash)['key1']['key2']['key3']
 151
Author: dbenhur,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-06-23 09:24:22

También puedes usar Object#andand.

my_hash['key1'].andand['key2'].andand['key3']
 6
Author: Sergio Tulentsev,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-04-12 20:24:15

Las condiciones my_hash['key1'] && my_hash['key1']['key2'] no se sienten SECAS.

Alternativas:

1) autovivificación magia. De ese post:

def autovivifying_hash
   Hash.new {|ht,k| ht[k] = autovivifying_hash}
end

Entonces, con tu ejemplo:

my_hash = autovivifying_hash     
my_hash['key1']['key2']['key3']

Es similar al Hash.fetch enfoque en que ambos operan con nuevos hashes como valores predeterminados, pero esto mueve los detalles a la hora de creación. Es cierto que esto es un poco de engaño: nunca devolverá 'nil' solo un hash vacío, que se crea sobre la marcha. Dependiendo de su caso de uso, esto podría ser derrochador.

2) Abstraer la estructura de datos con su mecanismo de búsqueda, y manejar 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 echar un vistazo a esto, Tal vez

 5
Author: inger,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-02-06 14:37:35