Équivalent de .try () pour un hachage afin d’éviter les erreurs de «méthode indéfinie» sur zéro?

Dans Rails, nous pouvons faire ce qui suit si une valeur n’existe pas pour éviter une erreur:

@myvar = @comment.try(:body) 

Quel est l’équivalent lorsque je suis en train de creuser un hash et que je ne veux pas avoir d’erreur?

 @myvar = session[:comments][@comment.id]["temp_value"] # [:comments] may or may not exist here 

Dans le cas ci-dessus, session[:comments]try[@comment.id] ne fonctionne pas. Quel serait?

Vous avez oublié de mettre un . avant l’ try :

 @myvar = session[:comments].try(:[], @comment.id) 

puisque [] est le nom de la méthode lorsque vous faites [@comment.id] .

L’annonce de Ruby 2.3.0-preview1 inclut une introduction de l’opérateur de navigation Safe.

Un opérateur de navigation sécurisé, qui existe déjà en C #, Groovy et Swift, est introduit pour faciliter la gestion des nuls en tant que obj&.foo . Array#dig et Hash#dig sont également ajoutés.

Cela signifie à partir de 2.3 ci-dessous le code

 account.try(:owner).try(:address) 

peut être réécrit à

 account&.owner&.address 

Cependant, il faut faire attention à ce que & n’est pas une goutte en remplacement de #try . Jetez un oeil à cet exemple:

 > params = nil nil > params&.country nil > params = OpenStruct.new(country: "Australia") # > params&.country "Australia" > params&.country&.name NoMethodError: undefined method `name' for "Australia":Ssortingng from (pry):38:in `
' > params.try(:country).try(:name) nil

Il comprend également une manière similaire: Array#dig et Hash#dig . Alors maintenant

 city = params.fetch(:[], :country).try(:[], :state).try(:[], :city) 

peut être réécrit à

 city = params.dig(:country, :state, :city) 

Encore une fois, #dig ne réplique pas le comportement de #try . Alors faites attention aux valeurs renvoyées. Si params[:country] renvoie, par exemple, un entier, TypeError: Integer does not have #dig method qui sera TypeError: Integer does not have #dig method .

La plus belle solution est une ancienne réponse de Mladen Jablanović , car elle vous permet de creuser plus profondément le hachage qu’avec les .try() directs .try() , si vous voulez que le code soit toujours aussi joli:

 class Hash def get_deep(*fields) fields.inject(self) {|acc,e| acc[e] if acc} end end 

Vous devez faire attention aux divers objects (en particulier les params ), car les chaînes et les tableaux répondent également à: [], mais la valeur renvoyée peut ne pas correspondre à ce que vous souhaitez, et Array déclenche une exception pour les chaînes ou les symboles utilisés comme index.

C’est la raison pour laquelle, dans la forme suggérée de cette méthode (ci-dessous), le test (généralement moche) pour .is_a?(Hash) est utilisé au lieu de (généralement mieux) .respond_to?(:[]) :

 class Hash def get_deep(*fields) fields.inject(self) {|acc,e| acc[e] if acc.is_a?(Hash)} end end a_hash = {:one => {:two => {:three => "asd"}, :arr => [1,2,3]}} puts a_hash.get_deep(:one, :two ).inspect # => {:three=>"asd"} puts a_hash.get_deep(:one, :two, :three ).inspect # => "asd" puts a_hash.get_deep(:one, :two, :three, :four).inspect # => nil puts a_hash.get_deep(:one, :arr ).inspect # => [1,2,3] puts a_hash.get_deep(:one, :arr, :too_deep ).inspect # => nil 

Le dernier exemple déclencherait une exception : “Symbole en tant que tableau index (TypeError)” s’il n’était pas protégé par ce laid “is_a? (Hash)”.

L’utilisation correcte de try avec un hash est @sesion.try(:[], :comments) .

 @session.try(:[], :comments).try(:[], commend.id).try(:[], 'temp_value') 

Mise à jour: à partir de Ruby 2.3, utilisez #dig

La plupart des objects qui répondent à [] attendent un argument Integer, Hash étant une exception qui acceptera tout object (comme des chaînes ou des symboles).

Ce qui suit est une version légèrement plus robuste de la réponse d’ Arsen7 qui prend en charge les systèmes nesteds Array, Hash, ainsi que tous les autres objects qui attendent un entier transmis à [].

Ce n’est pas infaillible, car quelqu’un peut avoir créé un object qui implémente [] et n’accepte pas un argument Integer. Cependant, cette solution fonctionne très bien dans le cas courant, par exemple en extrayant des valeurs nestedes de JSON (qui a à la fois Hash et Array):

 class Hash def get_deep(*fields) fields.inject(self) { |acc, e| acc[e] if acc.is_a?(Hash) || (e.is_a?(Integer) && acc.respond_to?(:[])) } end end 

Il peut être utilisé de la même manière que la solution d’Arsen7, mais supporte également les tableaux, par exemple

 json = { 'users' => [ { 'name' => { 'first_name' => 'Frank'} }, { 'name' => { 'first_name' => 'Bob' } } ] } json.get_deep 'users', 1, 'name', 'first_name' # Pulls out 'Bob' 
 @myvar = session.fetch(:comments, {}).fetch(@comment.id, {})["temp_value"] 

Depuis Ruby 2.0, vous pouvez faire:

 @myvar = session[:comments].to_h[@comment.id].to_h["temp_value"] 

À partir de Ruby 2.3, vous pouvez faire:

 @myvar = session.dig(:comments, @comment.id, "temp_value") 

disons que vous voulez trouver params[:user][:email] mais vous ne savez pas si l’ user est présent ou non dans les params . Alors-

tu peux essayer:

 params[:user].try(:[], :email) 

Il retournera soit nil (si l’ user n’est pas là ou email n’est pas là dans l’ user ) ou autrement la valeur de l’ email dans l’ user .

Depuis Ruby 2.3, cela devient un peu plus facile. Au lieu d’avoir à imbriquer des instructions d’ try ou à définir votre propre méthode, vous pouvez maintenant utiliser Hash#dig ( documentation ).

 h = { foo: {bar: {baz: 1}}} h.dig(:foo, :bar, :baz) #=> 1 h.dig(:foo, :zot) #=> nil 

Ou dans l’exemple ci-dessus:

 session.dig(:comments, @comment.id, "temp_value") 

Cela présente l’avantage supplémentaire d’être plus try de certains des exemples ci-dessus. Si l’un des arguments conduit au retour du hachage, il ne répondra plus.

Une autre approche:

 @myvar = session[:comments][@comment.id]["temp_value"] rescue nil 

Cela pourrait aussi être considéré comme un peu dangereux car il peut trop cacher, personnellement je l’aime.

Si vous voulez plus de contrôle, vous pouvez envisager quelque chose comme:

 def handle # just an example name, use what speaks to you raise $! unless $!.kind_of? NoMethodError # Do whatever checks or # reporting you want end # then you may use @myvar = session[:comments][@comment.id]["temp_value"] rescue handle 

Lorsque vous faites cela:

 myhash[:one][:two][:three] 

Vous ne faites qu’enchaîner un tas d’appels à une méthode “[]”, l’erreur se produit si myhash [: one] retourne nil, car nil n’a pas de méthode []. Ainsi, une méthode simple et plutôt pirate consiste à append une méthode [] à Niclass, qui renvoie nil: je définirais ceci dans une application rails comme suit:

Ajouter la méthode:

 #in lib/ruby_extensions.rb class NilClass def [](*args) nil end end 

Exiger le fichier:

 #in config/initializers/app_environment.rb require 'ruby_extensions' 

Maintenant, vous pouvez appeler des hachages nesteds sans crainte: je fais la démonstration dans la console ici:

 >> hash = {:foo => "bar"} => {:foo=>"bar"} >> hash[:foo] => "bar" >> hash[:doo] => nil >> hash[:doo][:too] => nil 

La réponse d’Andrew n’a pas fonctionné pour moi quand j’ai réessayé cela récemment. Peut-être que quelque chose a changé?

 @myvar = session[:comments].try('[]', @comment.id) 

Le '[]' est entre guillemets au lieu d’un symbole :[]

Essayez d’utiliser

 @myvar = session[:comments][@comment.id]["temp_value"] if session[:comments]