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?
Sie haben vergessen, ein .
vor dem try
:
@myvar = session[:comments].try(:[], @comment.id)
schon seit []
ist der Name der Methode, wenn Sie [@comment.id]
.
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
undHash#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.
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. .
Die korrekte Verwendung von try mit einem Hash ist @sesion.try(:[], :comments)
.
@session.try(:[], :comments).try(:[], commend.id).try(:[], 'temp_value')
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'
@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")
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.
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
.
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
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
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 :[]