2012-04-12 10 views
70

Duplicar posibles:
Ruby: Nils in an IF statement
Is there a clean way to avoid calling a method on nil in a nested params hash?Ruby - El acceso de hash multidimensional y evitar el acceso no nula objeto

Digamos que intento acceder a un hash de esta manera:

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

Esto es bueno si key1, ke y2 y key3 existen en el 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 encargo de esto haciendo un condicional como:

if my_hash['key1'] && my_hash['key1']['key2'] ...

Es éste el caso, ¿hay alguna otra manera Rubiest de hacerlo?

+1

Lo sentimos, la comunidad. Traté de buscar esto y no pude encontrarlo. Cierra si es apropiado. – Nobita

+0

La respuesta aceptada menciona todos los métodos posibles excepto el correcto para Ruby 2.3+: http://ruby-doc.org/core-2.3.1/Hash.html#method-i-dig –

Respuesta

139

Hay muchos enfoques para esto.

Si utiliza Rubí 2.3 o superior, puede utilizar dig

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

Un montón de gente se adhieren a ruby ​​simple y cadena de las pruebas && guardia.

Usted podría utilizar stdlib Hash#fetch también:

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

Algunos como método de encadenamiento de ActiveSupport #try.

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

Otros utilizan andand

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

Algunas personas piensan egocentric nils son una buena idea (aunque alguien podría perseguirte y torturarte si encontraron lo hace).

class NilClass 
    def method_missing(*args); nil; end 
end 

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

Puede usar Enumerable#reduce (o alias inject).

['key1','key2','key3'].reduce(my_hash) {|m,k| m && m[k] } 

O quizás extender Hash o simplemente el objeto hash objetivo con un método de consulta anidada

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 olvidar la mónada maybe?

Maybe.new(my_hash)['key1']['key2']['key3'] 
+1

Decidí hacer el enfoque fetch . Pero gracias por todas las sugerencias. – Nobita

+0

También puedes probar la gema monádica, que tiene un Quizás (y otras mónadas) que ayudan a manejar excepciones –

+0

¿Qué piensas sobre el uso de 'rescue nil' al final del enunciado? – jakeonrails

5

Condiciones my_hash['key1'] && my_hash['key1']['key2'] no siento DRY.

Alternativas:

1) autovivification mágicos. A partir de ese mensaje:

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

Entonces, con su 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 por defecto, pero esto mueve detalles para la creación hora. 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 simple ejemplo:

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 se siente monádica se puede echar un vistazo a esto, Maybe

5

También es posible usar Object#andand.

my_hash['key1'].andand['key2'].andand['key3'] 
Cuestiones relacionadas