Equivalente a.try () para un hash para evitar errores de "método indefinido" en nil? [duplicar]


Esta pregunta ya tiene una respuesta aquí:

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?

Author: user22a6db72d7249, 2011-06-03

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

 240
Author: Andrew Grimm,
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 y Hash#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á.

 54
Author: baxang,
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)".

 25
Author: Arsen7,
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')
 13
Author: Pablo Castellazzi,
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'
 13
Author: Benjamin Dobell,
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")
 11
Author: sawa,
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.

 9
Author: Rajesh Paul,
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.

 7
Author: Steve Smith,
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
 6
Author: Nicolas Goy,
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
 2
Author: Max Williams,
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 :[]

 1
Author: claptimes,
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]
 -1
Author: bor1s,
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