web-dev-qa-db-fra.com

Équivalent de .try () pour un hachage pour éviter les erreurs "méthode indéfinie" sur nil?

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?

161
sscirrus

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].

258
Andrew Grimm

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 Et Hash#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é.

61
baxang

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)" .

25
Arsen7

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

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

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'
14
Benjamin Dobell
@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")
12
sawa

À 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.

11
Steve Smith

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.

11
Rajesh Paul

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
6
Nicolas Goy

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
2
Max Williams

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 :[]

1
claptimes