2012-04-12 12 views
70

Eventuali duplicati:
Ruby: Nils in an IF statement
Is there a clean way to avoid calling a method on nil in a nested params hash?Rubino - Accesso hash multidimensionale ed evitare l'accesso nil oggetto

Diciamo che provo ad accedere a un hash come questo:

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

Questo è bello se key1, ke y2 e key3 esistono nell'hash (es), ma cosa succede se, ad esempio, key1 non esiste?

Quindi vorrei ottenere NoMethodError: undefined method [] for nil:NilClass. E a nessuno piace.

Finora mi occupo di questo facendo un simile condizionale:

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

È questo il caso, c'è qualche altro modo Rubiest di farlo?

+1

Scusa comunità. Ho provato a cercarlo e non sono riuscito a trovarlo. Chiudi se appropriato. – Nobita

+0

La risposta accettata cita ogni metodo possibile eccetto quello corretto per Ruby 2.3+: http://ruby-doc.org/core-2.3.1/Hash.html#method-i-dig –

risposta

139

Ci sono molti approcci a questo.

Se si utilizza Rubino 2.3 o superiore, è possibile utilizzare dig

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

Un sacco di gente bastone per ruby ​​pianura e la catena delle && test di guardia.

Si potrebbe utilizzare stdlib Hash#fetch troppo:

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

Alcuni, come il concatenamento il metodo di ActiveSupport #try.

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

Altri usano andand

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

Alcune persone pensano egocentric nils sono una buona idea (anche se qualcuno potrebbe darti la caccia e si tortura se hanno trovato si esegue questa operazione).

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

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

È possibile utilizzare Enumerable#reduce (o inserire alias).

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

O forse estendere Hash o semplicemente l'oggetto hash bersaglio con un metodo di ricerca annidato

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, e come non ricordare il maybe Monade?

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

Ho deciso di fare l'approccio di recupero . Ma grazie per tutti i suggerimenti. – Nobita

+0

Puoi anche provare la gemma monadica, che ha una Forse (e altre monadi) che aiutano nella gestione delle eccezioni –

+0

Quali sono i tuoi pensieri sull'uso di 'rescue nil' alla fine dell'istruzione? – jakeonrails

5

Condizioni my_hash['key1'] && my_hash['key1']['key2'] non si sentono DRY.

Alternative:

1) autovivification magici. Da quel post:

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

Poi, con il tuo esempio:

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

E 'simile all'approccio Hash.fetch nel senso che entrambi operano con nuovi hash come valori di default, ma questo sposta dettagli per la creazione tempo. Certo, questo è un po 'di imbroglio: non restituirà mai' nil 'solo un hash vuoto, che viene creato al volo. A seconda del tuo caso d'uso, questo potrebbe essere uno spreco.

2) Estrarre la struttura dati con il relativo meccanismo di ricerca e gestire il caso non trovato dietro le quinte. Un esempio semplice:

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) Se ti senti monade si può dare un'occhiata a questo, Maybe

5

Si potrebbe anche usare Object#andand.

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