web-dev-qa-db-de.com

Entspricht .try () für einen Hash, um "undefinierte Methoden" -Fehler auf nil zu vermeiden?

In Rails können wir Folgendes tun, falls ein Wert nicht existiert, um einen Fehler zu vermeiden:

@myvar = @comment.try(:body)

Was ist das Äquivalent, wenn ich tief in einen Hash grabe und keinen Fehler erhalten möchte?

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

Im obigen Fall ist session[:comments]try[@comment.id] funktioniert nicht. Was würde?

161
sscirrus

Sie haben vergessen, ein . vor dem try:

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

schon seit [] ist der Name der Methode, wenn Sie [@comment.id].

258
Andrew Grimm

Die Ankündigung von Ruby 2.3.0-preview1 enthält eine Einführung in den Safe Navigation Operator.

Ein Operator für die sichere Navigation, der bereits in C #, Groovy und Swift vorhanden ist, wird eingeführt, um die Handhabung zu vereinfachen, da obj&.foo. Array#Dig und Hash#Dig werden ebenfalls hinzugefügt.

Dies bedeutet ab 2.3 unter Code

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

kann umgeschrieben werden

account&.owner&.address

Man sollte jedoch aufpassen, dass & ist kein Tropfen als Ersatz für #try. Schauen Sie sich dieses Beispiel an:

> 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

Es enthält auch eine ähnliche Art und Weise: Array#Dig und Hash#Dig. Also jetzt das

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

kann umgeschrieben werden

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

Nochmal, #Dig repliziert nicht #try Verhalten. Seien Sie also vorsichtig, wenn Sie Werte zurückgeben. Ob params[:country] gibt beispielsweise eine Ganzzahl zurück, TypeError: Integer does not have #Dig method wird ausgelöst.

61
baxang

Die schönste Lösung ist ein altes Antwort von Mladen Jablanović , da Sie mit direkten .try() - Aufrufen tiefer in den Hash graben können, als Sie könnten, wenn Sie den Code noch wollen schön aussehen:

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

Sie sollten mit verschiedenen Objekten (insbesondere params) vorsichtig sein, da Zeichenfolgen und Arrays auch auf Folgendes reagieren: [], aber der zurückgegebene Wert entspricht möglicherweise nicht Ihren Wünschen, und Array löst eine Ausnahme für Zeichenfolgen oder Symbole aus, die als Indizes verwendet werden .

Das ist der Grund, warum in der vorgeschlagenen Form dieser Methode (unten) der (normalerweise hässlich) Test für .is_a?(Hash) wird anstelle von (normalerweise besser).respond_to?(:[]) verwendet:

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

Das letzte Beispiel würde eine Ausnahme auslösen: "Symbol als Array-Index (TypeError)", wenn es nicht von diesem hässlichen "is_a? (Hash)" geschützt würde. .

25
Arsen7

Die korrekte Verwendung von try mit einem Hash ist @sesion.try(:[], :comments).

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

Update: Stand Ruby 2.3 use #Dig

Die meisten Objekte, die auf [] antworten, erwarten ein Integer-Argument, wobei Hash eine Ausnahme darstellt, die alle Objekte akzeptiert (z. B. Zeichenfolgen oder Symbole).

Das Folgende ist eine etwas robustere Version von Arsen7s Antwort , die verschachteltes Array, Hash sowie alle anderen Objekte unterstützt, die erwarten, dass eine Ganzzahl an [] übergeben wird.

Dies ist kein Narrenbeweis, da möglicherweise jemand ein Objekt erstellt hat, das [] implementiert und nicht ein Integer-Argument akzeptiert. Diese Lösung funktioniert jedoch im allgemeinen Fall sehr gut, z. Verschachtelte Werte aus JSON ziehen (das sowohl Hash als auch Array enthält):

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

Es kann wie die Lösung von Arsen7 verwendet werden, unterstützt jedoch auch Arrays, z.

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

Ab Ruby 2.0 können Sie Folgendes tun:

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

Ab Ruby 2.3 können Sie Folgendes tun:

@myvar = session.Dig(:comments, @comment.id, "temp_value")
12
sawa

Ab Ruby 2.3 wird dies etwas einfacher. Anstatt try Anweisungen zu verschachteln oder eine eigene Methode zu definieren, können Sie jetzt Hash#Dig ( Dokumentation ).

h = { foo: {bar: {baz: 1}}}

h.Dig(:foo, :bar, :baz)           #=> 1
h.Dig(:foo, :zot)                 #=> nil

Oder im obigen Beispiel:

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

Dies hat den zusätzlichen Vorteil, dass es mehr wie try ist als einige der obigen Beispiele. Wenn eines der Argumente dazu führt, dass der Hash nil zurückgibt, antwortet er nil.

11
Steve Smith

angenommen, Sie möchten params[:user][:email] finden, aber es ist nicht sicher, ob user in params vorhanden ist oder nicht. Dann-

du kannst es versuchen:

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

Es wird entweder nil (wenn user nicht da ist oder email nicht da ist in user) oder auf andere Weise der Wert zurückgegeben von email in user.

11
Rajesh Paul

Ein anderer Ansatz:

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

Dies könnte auch ein bisschen gefährlich sein, weil es zu viel verbergen kann, ich persönlich mag es.

Wenn Sie mehr Kontrolle wünschen, können Sie Folgendes in Betracht ziehen:

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

Wenn Sie dies tun:

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

Sie verketten nur eine Reihe von Aufrufen mit einer "[]" - Methode, und der Fehler tritt auf, wenn myhash [: one] nil zurückgibt, da nil keine [] -Methode hat. Ein einfacher und ziemlich hackiger Weg ist es, Niclass eine [] -Methode hinzuzufügen, die nil zurückgibt: Ich würde dies in einer Rails App wie folgt einrichten:

Fügen Sie die Methode hinzu:

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

Benötigen Sie die Datei:

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

Jetzt können Sie verschachtelte Hashes ohne Angst aufrufen: Ich demonstriere in der Konsole hier:

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

Andrews Antwort hat bei mir nicht funktioniert, als ich es kürzlich noch einmal versuchte. Vielleicht hat sich etwas geändert?

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

Das '[]' steht in Anführungszeichen anstelle eines Symbols :[]

1
claptimes