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
etHash#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]