Dans Rails, nous pouvons procéder comme suit si une valeur n’existait pas pour éviter une erreur:
@myvar = @comment.try(:body)
Quel est l'équivalent lorsque je creuse dans un hachage et que je ne veux pas 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 le try
:
@myvar = session[:comments].try(:[], @comment.id)
puisque []
est le nom de la méthode quand vous faites [@comment.id]
.
L’annonce de Ruby 2.3.0-preview1 inclut une introduction à l’opérateur de navigation sécurisée.
Un opérateur de navigation sûr, qui existe déjà en C #, Groovy et Swift, est introduit pour faciliter la gestion des néant en tant que
obj&.foo
.Array#Dig
EtHash#Dig
Sont également ajoutés.
Cela signifie qu'à partir de 2.3 ci-dessous le code
account.try(:owner).try(:address)
peut être réécrit pour
account&.owner&.address
Cependant, il faut faire attention à ce que &
Ne remplace pas #try
. Jetez un oeil à cet exemple:
> params = nil
nil
> params&.country
nil
> params = OpenStruct.new(country: "Australia")
#<OpenStruct country="Australia">
> params&.country
"Australia"
> params&.country&.name
NoMethodError: undefined method `name' for "Australia":String
from (pry):38:in `<main>'
> params.try(:country).try(:name)
nil
Cela inclut également une sorte de méthode similaire: Array#Dig
Et Hash#Dig
. Alors maintenant, ce
city = params.fetch(:[], :country).try(:[], :state).try(:[], :city)
peut être réécrit pour
city = params.Dig(:country, :state, :city)
Encore une fois, #Dig
Ne reproduit pas le comportement de #try
. Alors soyez prudent avec les valeurs renvoyées. Si params[:country]
Est renvoyé, par exemple, un entier, TypeError: Integer does not have #Dig method
Sera généré.
La plus belle solution est un vieux réponse de Mladen Jablanović , car il vous permet de creuser plus profondément dans le hachage que si vous utilisiez des appels directs .try()
, si vous voulez conserver le code être joli:
class Hash
def get_deep(*fields)
fields.inject(self) {|acc,e| acc[e] if acc}
end
end
Soyez prudent avec divers objets (en particulier params
), car Strings et Arrays répondent également à: [], mais la valeur renvoyée peut ne pas correspondre à vos souhaits et Array déclenche une exception pour les chaînes ou les symboles utilisés comme index. .
C’est la raison pour laquelle la forme suggérée de cette méthode (ci-dessous) le (généralement moche) test pour .is_a?(Hash)
est utilisé à la place de (généralement meilleur).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 lève une exception : "Symbol as index de tableau (TypeError)" s'il n'est pas protégé par cet horrible "is_a? (Hash)" .
L'utilisation correcte de try avec un hachage est @sesion.try(:[], :comments)
.
@session.try(:[], :comments).try(:[], commend.id).try(:[], 'temp_value')
Mise à jour: À partir de Ruby 2.3 utiliser #Dig
La plupart des objets qui répondent à [] attendent un argument Integer, Hash étant une exception pouvant accepter n'importe quel objet (tel que des chaînes ou des symboles).
Ce qui suit est une version légèrement plus robuste de réponse d'Arsen7 qui prend en charge les tableaux imbriqués, Hash, ainsi que tous les autres objets dans lesquels un entier doit être passé à [].
Ce n'est pas une preuve irréfutable, car quelqu'un peut avoir créé un objet qui implémente [] et ne le fait pas accepte un argument Integer. Cependant, cette solution fonctionne très bien dans le cas courant, par exemple. extraire les valeurs imbriquées de JSON (qui a à la fois un hachage et un tableau):
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 prend également en charge 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"]
De Ruby 2.0, vous pouvez faire:
@myvar = session[:comments].to_h[@comment.id].to_h["temp_value"]
De Ruby 2.3, vous pouvez faire:
@myvar = session.Dig(:comments, @comment.id, "temp_value")
À partir de Ruby 2.3, cela devient un peu plus facile. Au lieu de devoir imbriquer des instructions try
ou de 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 a l’avantage supplémentaire de ressembler davantage à try
qu'à certains des exemples ci-dessus. Si l'un des arguments conduit au hash retournant nil, il répondra nil.
dites que vous voulez trouver params[:user][:email]
mais il n'est pas certain que user
soit présent ou non dans params
. Ensuite-
tu peux essayer:
params[:user].try(:[], :email)
Il retournera soit nil
(si user
n’est pas là ou email
n’est pas là dans user
) ou sinon la valeur de email
dans user
.
Une autre approche:
@myvar = session[:comments][@comment.id]["temp_value"] rescue nil
Cela peut aussi être considéré comme un peu dangereux car cela peut en cacher trop, personnellement, je l'aime bien.
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
Quand tu fais ça:
myhash[:one][:two][:three]
Vous êtes en train de chaîner plusieurs appels à une méthode "[]". Une erreur se produit si myhash [: one] ne renvoie pas, car nil n'a pas de méthode []. Donc, un moyen simple et plutôt hacky est d’ajouter une méthode [] à Niclass, qui renvoie nil: je l’installerais dans une 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 les hash imbriqués sans crainte: je suis en train de démontrer 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 lorsque 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 :[]