web-dev-qa-db-fra.com

Ruby - Accédez au hachage multidimensionnel et évitez d'accéder à aucun objet

Duplicata possible:
Ruby: Nils dans une instruction IF
Existe-t-il un moyen propre d'éviter d'appeler une méthode sur nil dans un hachage de paramètres imbriqués?

Disons que j'essaie d'accéder à un hachage comme celui-ci:

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

C'est bien si key1, key2 et key3 existent dans le (s) hachage (s), mais que faire si, par exemple key1 n'existe pas?

Ensuite, je recevrais NoMethodError: undefined method [] for nil:NilClass. Et personne n'aime ça.

Jusqu'à présent, je traite avec cela en faisant un conditionnel comme:

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

Est-ce approprié, existe-t-il une autre manière Rubiest de le faire?

85
Nobita

Il existe de nombreuses approches à ce sujet.

Si vous utilisez Ruby 2.3 ou supérieur, vous pouvez utiliser Dig

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

Beaucoup de gens s'en tiennent à simple Ruby et enchaîne le && tests de garde.

Vous pouvez également utiliser stdlib Hash # fetch :

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

Certains aiment enchaîner la méthode # try d'ActiveSupport.

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

D'autres utilisent etand

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

Certaines personnes pensent que nils égocentriques sont une bonne idée (bien que quelqu'un puisse vous traquer et vous torturer s'il vous trouve faire cela).

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

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

Vous pouvez utiliser Enumerable # reduction (ou alias inject).

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

Ou peut-être étendre Hash ou simplement votre objet de hachage cible avec une méthode de recherche imbriquée

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, et comment pourrions-nous oublier la peut-être monade?

Maybe.new(my_hash)['key1']['key2']['key3']
168
dbenhur

Vous pouvez également utiliser Object # andand .

my_hash['key1'].andand['key2'].andand['key3']
6
Sergio Tulentsev

Conditions my_hash['key1'] && my_hash['key1']['key2'] ne se sent pas SEC .

Alternatives:

1) autovivification magie. De ce poste:

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

Ensuite, avec votre exemple:

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

Elle est similaire à l'approche Hash.fetch dans la mesure où les deux fonctionnent avec de nouveaux hachages comme valeurs par défaut, mais cela déplace les détails au moment de la création. Certes, c'est un peu de la triche: il ne retournera jamais "nul" juste un hachage vide, qui est créé à la volée. Selon votre cas d'utilisation, cela pourrait être un gaspillage.

2) Éloignez la structure de données avec son mécanisme de recherche et gérez le cas non trouvé dans les coulisses. Un exemple simpliste:

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 vous vous sentez monadique, vous pouvez jeter un œil à cela, Peut-être

5
inger