Equivalente a.try () para un hash para evitar errores de "método indefinido" en nil? [duplicar]
Esta pregunta ya tiene una respuesta aquí:
- ¿Cómo evitar NoMethodError para elementos nil cuando se accede a hashes anidados? [duplicar] 4 respuestas
En Rails podemos hacer lo siguiente en caso de que no exista un valor para evitar un error:
@myvar = @comment.try(:body)
¿Cuál es el equivalente cuando estoy cavando profundamente en un hash y no quiero obtener un ¿error?
@myvar = session[:comments][@comment.id]["temp_value"]
# [:comments] may or may not exist here
En el caso anterior, session[:comments]try[@comment.id]
no funciona. ¿Qué lo haría?
12 answers
Olvidaste poner un .
antes del try
:
@myvar = session[:comments].try(:[], @comment.id)
Puesto que []
es el nombre del método cuando lo haces [@comment.id]
.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-05-01 04:29:14
El anuncio de Ruby 2.3.0-preview1 incluye una introducción de operador de navegación segura.
Un operador de navegación segura, que ya existe en C#, Groovy y Swift, se introduce para facilitar el manejo de cero como
obj&.foo
.Array#dig
yHash#dig
también se añaden.
Esto significa a partir del código 2.3 debajo
account.try(:owner).try(:address)
Se puede reescribir a
account&.owner&.address
Sin embargo, uno debe tener cuidado de que &
no es una caída en el reemplazo de #try
. Echa un vistazo en este ejemplo:
> 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
También incluye una forma similar: Array#dig
y Hash#dig
. Así que ahora esto
city = params.fetch(:[], :country).try(:[], :state).try(:[], :city)
Se puede reescribir a
city = params.dig(:country, :state, :city)
Nuevamente, #dig
no está replicando el comportamiento de #try
. Así que tenga cuidado con la devolución de valores. Si params[:country]
devuelve, por ejemplo, un entero, TypeError: Integer does not have #dig method
se levantará.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-09-14 04:22:43
La solución más hermosa es una antigua respuesta de Mladen Jablanović , ya que te permite profundizar en el hash más de lo que podrías con el uso de llamadas directas .try()
, si quieres que el código aún se vea bien:
class Hash
def get_deep(*fields)
fields.inject(self) {|acc,e| acc[e] if acc}
end
end
Debe tener cuidado con varios objetos (especialmente params
), porque las cadenas y los Arrays también responden a: [], pero el valor devuelto puede no ser el que desea, y Array genera una excepción para las Cadenas o Símbolos utilizados como índices.
Esa es la razón por la que en el forma sugerida de este método (a continuación) se usa la prueba (generalmente fea) para .is_a?(Hash)
en lugar de (generalmente mejor) .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
El último ejemplo levantaría una excepción: "Symbol as array index (TypeError)" si no estaba guardado por este feo "is_a?(Hash)".
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 12:26:17
El uso correcto de try con un hash es @sesion.try(:[], :comments)
.
@session.try(:[], :comments).try(:[], commend.id).try(:[], 'temp_value')
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-06-03 09:30:58
Actualizar: A partir de Ruby 2.3 use #dig
La mayoría de los objetos que responden a [] esperan un argumento Entero, siendo Hash una excepción que aceptará cualquier objeto (como cadenas o símbolos).
La siguiente es una versión ligeramente más robusta de respuesta de Arsen7 que soporta Matriz anidada, Hash, así como cualquier otro objeto que espere un Entero pasado a [].
No es infalible, ya que alguien puede haber creado un objeto que implementa [] y no acepta un argumento Entero. Sin embargo, esta solución funciona muy bien en el caso común, por ejemplo, extrayendo valores anidados de JSON (que tiene tanto Hash como 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
Se puede utilizar lo mismo que la solución de Arsen7, pero también admite matrices, por ejemplo,
json = { 'users' => [ { 'name' => { 'first_name' => 'Frank'} }, { 'name' => { 'first_name' => 'Bob' } } ] }
json.get_deep 'users', 1, 'name', 'first_name' # Pulls out 'Bob'
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 10:31:19
@myvar = session.fetch(:comments, {}).fetch(@comment.id, {})["temp_value"]
Desde Ruby 2.0, puedes hacer:
@myvar = session[:comments].to_h[@comment.id].to_h["temp_value"]
Desde Ruby 2.3, puedes hacer:
@myvar = session.dig(:comments, @comment.id, "temp_value")
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-12-20 14:36:24
Digamos que quieres encontrar params[:user][:email]
pero no es seguro si user
está ahí en params
o no. Entonces
Puedes probar:
params[:user].try(:[], :email)
Volverá nil
(if user
is not there or email
is not there in user
) or otherwise the value of email
in user
.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-01-23 07:07:46
A partir de Ruby 2.3 esto se vuelve un poco más fácil. En lugar de tener que anidar sentencias try
o definir su propio método, ahora puede usar Hash#dig
(documentación ).
h = { foo: {bar: {baz: 1}}}
h.dig(:foo, :bar, :baz) #=> 1
h.dig(:foo, :zot) #=> nil
O en el ejemplo anterior:
session.dig(:comments, @comment.id, "temp_value")
Esto tiene el beneficio adicional de ser más parecido a try
que algunos de los ejemplos anteriores. Si alguno de los argumentos lleva a que el hash devuelva nil, entonces responderá nil.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-11-20 14:27:58
Otro enfoque:
@myvar = session[:comments][@comment.id]["temp_value"] rescue nil
Esto también podría considerarse un poco peligroso porque puede ocultar demasiado, personalmente me gusta.
Si quieres más control, puedes considerar algo como:
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
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-07-16 21:01:24
Cuando haces esto:
myhash[:one][:two][:three]
Solo estás encadenando un montón de llamadas a un método " []", un error ocurre si myhash[:one] devuelve nil, porque nil no tiene un método []. Por lo tanto, una manera simple y bastante hacky es agregar un método [] a Niclass, que devuelve nil: configuraría esto en una aplicación rails de la siguiente manera:
Añadir el método:
#in lib/ruby_extensions.rb
class NilClass
def [](*args)
nil
end
end
Requiere el archivo:
#in config/initializers/app_environment.rb
require 'ruby_extensions'
Ahora puedes llamar hashes anidados sin miedo: estoy demostrando en la consola aquí:
>> hash = {:foo => "bar"}
=> {:foo=>"bar"}
>> hash[:foo]
=> "bar"
>> hash[:doo]
=> nil
>> hash[:doo][:too]
=> nil
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-06-03 10:03:34
La respuesta de Andrew no funcionó para mí cuando intenté esto de nuevo recientemente. Tal vez algo ha cambiado?
@myvar = session[:comments].try('[]', @comment.id)
El '[]'
está entre comillas en lugar de un símbolo :[]
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-12-23 13:00:00
Intenta usar
@myvar = session[:comments][@comment.id]["temp_value"] if session[:comments]
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-06-03 08:49:59