Metaprogramación Ruby: nombres de variables de instancia dinámicas

94

Digamos que tengo el siguiente hash:

{ :foo => 'bar', :baz => 'qux' }

¿Cómo podría establecer dinámicamente las claves y los valores para que se conviertan en variables de instancia en un objeto ...

class Example
  def initialize( hash )
    ... magic happens here...
  end
end

... para que termine con lo siguiente dentro del modelo ...

@foo = 'bar'
@baz = 'qux'

?

Andrés
fuente

Respuestas:

168

El método que está buscando es instance_variable_set. Entonces:

hash.each { |name, value| instance_variable_set(name, value) }

O, más brevemente,

hash.each &method(:instance_variable_set)

Si a sus nombres de variables de instancia les falta el "@" (como están en el ejemplo del OP), deberá agregarlos, por lo que sería más como:

hash.each { |name, value| instance_variable_set("@#{name}", value) }
Arrojar
fuente
18
No funcionó para mí para 1.9.3. Usé esto en su lugarhash.each {|k,v| instance_variable_set("@#{k}",v)}
Andrei
3
otra razón más para amar Ruby
jschorr
¿Puede explicar cómo hash.each &method(:instance_variable_set), el método instance_variable_setrecibe los dos parámetros que necesita?
Arnold Roa
¿Alguna idea de cómo hacer esto de forma recursiva? (si hay varios niveles en el hash de entrada)
nemenems
13
h = { :foo => 'bar', :baz => 'qux' }

o = Struct.new(*h.keys).new(*h.values)

o.baz
 => "qux" 
o.foo
 => "bar" 
DigitalRoss
fuente
1
Eso es bastante interesante ... ¿qué está .new()haciendo exactamente el segundo encadenado ?
Andrew
3
@Andrew: Struct.newcrea una nueva clase basada en las claves hash, y luego la segunda crea newel primer objeto de la clase recién creada, inicializándolo con los valores del Hash. Ver ruby-doc.org/core-1.8.7/classes/Struct.html
DigitalRoss
2
En realidad, esta es una excelente manera de hacerlo, ya que para esto está hecho Struct.
Chuck
2
O use OpenStruct . require 'ostruct'; h = {:foo => 'foo'}; o = OpenStruct.new(h); o.foo == 'foo'
Justin Force
Tuve que asignar mis claves a los símbolos:Struct.new(*hash.keys.map { |str| str.to_sym }).new(*hash.values)
erran
7

Haces que nos den ganas de llorar :)

En cualquier caso, consulte Object#instance_variable_gety Object#instance_variable_set.

Codificación feliz.


fuente
eh sí, no pude evitar preguntarme ... ¿por qué? ¿Cuándo sería un buen momento para usar esto?
Zach Smith
por ejemplo, podría querer tener una set_entitydevolución de llamada genérica para todos los controladores y no quiero interferir con las variables de instancia existentesdef set_entity(name, model); instance_variable_set(name, model.find_by(params[:id])); end;
user1201917
5

También puede usar lo sendque evita que el usuario establezca variables de instancia inexistentes:

def initialize(hash)
  hash.each { |key, value| send("#{key}=", value) }
end

Úselo sendcuando en su clase haya un setter como attr_accessorpara sus variables de instancia:

class Example
  attr_accessor :foo, :baz
  def initialize(hash)
    hash.each { |key, value| send("#{key}=", value) }
  end
end
Asarluhi
fuente