tutorial rails que ejemplos curso caracteristicas ruby-on-rails ruby ruby-on-rails-3 hash

ruby on rails - rails - ¿Equivalente a.try() para un hash para evitar errores de "método indefinido" en nil?



ruby on rails tutorial (12)

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 profundizando 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, la session[:comments]try[@comment.id] no funciona. ¿Qué haría?


A partir de Ruby 2.3 esto se vuelve un poco más fácil. En lugar de tener que anidar, try declaraciones o defina su propio método, ahora puede usar Hash#dig ( #dig ).

h = { foo: {bar: {baz: 1}}} h.dig(:foo, :bar, :baz) #=> 1 h.dig(:foo, :zot) #=> nil

O en el ejemplo de arriba:

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

Esto tiene el beneficio adicional de ser más como try que algunos de los ejemplos anteriores. Si alguno de los argumentos lleva a que el hash devuelva nil, entonces responderá nil.


Cuando haces esto:

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

Estás encadenando un montón de llamadas a un método "[]", y el error ocurre si myhash [: uno] devuelve nil, porque nil no tiene un método []. Por lo tanto, una forma simple y bastante hacky es agregar un método [] a Niclass, que devuelve nil: lo configuraría en una aplicación de rieles de la siguiente manera:

Agrega el método:

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

Requerir el archivo:

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

Ahora puede invocar 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


El uso apropiado de try with hash es @sesion.try(:[], :comments) .

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


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


La solución más hermosa es una vieja respuesta de Mladen Jablanović , ya que le permite profundizar en el hash más profundo de lo que podría con el uso de .try() directas a .try() , si quiere que el código 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 ), ya que las cadenas y matrices también responden a: [], pero el valor devuelto puede no ser el que desea, y Array plantea una excepción para cadenas o símbolos utilizados como índices.

Esa es la razón por la cual en la forma sugerida de este método (a continuación .is_a?(Hash) se usa la prueba (usualmente fea) para .is_a?(Hash) lugar de (normalmente 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 generaría una excepción : "Símbolo como índice de matriz (TypeError)" si no estaba protegido por este feo "is_a? (Hash)".


Olvidaste poner un . antes del try :

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

ya que [] es el nombre del método cuando lo hace [@comment.id] .


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


Tratar de usar

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


supongamos que quiere encontrar params[:user][:email] pero no está seguro de si el user está allí en params o no. Entonces-

puedes probar:

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

Devolverá nil (si el user no está allí o el email no está en el user ) o el valor del email en el user .


El anuncio de Ruby 2.3.0-preview1 incluye una introducción al operador de navegación segura.

Un operador de navegación segura, que ya existe en C #, Groovy y Swift, se presenta para facilitar el manejo nulo como obj&.foo . Array#dig y Hash#dig también se agregan.

Esto significa que a partir del código 2.3 a continuación

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

puede ser reescrito para

account&.owner&.address

Sin embargo, uno debe tener cuidado de que & no sea una gota en reemplazo de #try . Echale un vistazo a éste 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 un tipo de camino similar: Array#dig y Hash#dig . Entonces ahora esto

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

puede ser reescrito para

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

De nuevo, #dig no está replicando el comportamiento de #try . Así que ten cuidado con devolver los valores. Si params[:country] devuelve, por ejemplo, un entero, TypeError: Integer does not have #dig method generará.


Actualización: a partir de Ruby 2.3 use #dig

La mayoría de los objetos que responden a [] esperan un argumento entero, con Hash siendo una excepción que aceptará cualquier objeto (como cadenas o símbolos).

La siguiente es una versión ligeramente más robusta de la respuesta de Arsen7 que admite Array, Hash anidados, así como cualquier otro objeto que espere que un entero pase a [].

No es infalible, ya que alguien puede haber creado un objeto que implemente [] y no acepte un argumento entero. Sin embargo, esta solución funciona muy bien en el caso común, por ejemplo, extraer 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 usar igual 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''


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