Métaprogrammation Ruby: noms de variables d'instances dynamiques

94

Disons que j'ai le hachage suivant:

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

Comment pourrais-je définir dynamiquement les clés et les valeurs pour devenir des variables d'instance dans un objet ...

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

... pour que je me retrouve avec ce qui suit à l'intérieur du modèle ...

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

?

Andrew
la source

Réponses:

168

La méthode que vous recherchez est instance_variable_set. Alors:

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

Ou, plus brièvement,

hash.each &method(:instance_variable_set)

Si vos noms de variables d'instance ne contiennent pas le "@" (comme dans l'exemple de l'OP), vous devrez les ajouter, donc ce serait plus comme:

hash.each { |name, value| instance_variable_set("@#{name}", value) }
Mandrin
la source
18
Cela n'a pas fonctionné pour moi pour 1.9.3. Je l'ai utilisé à la placehash.each {|k,v| instance_variable_set("@#{k}",v)}
Andrei
3
encore une autre raison d'aimer Ruby
jschorr
pouvez-vous expliquer comment hash.each &method(:instance_variable_set), la méthode instance_variable_setreçoit les deux paramètres dont elle a besoin?
Arnold Roa
une idée comment faire cela de manière récursive? (s'il y a plusieurs niveaux dans le hachage d'entrée)
nemenems
13
h = { :foo => 'bar', :baz => 'qux' }

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

o.baz
 => "qux" 
o.foo
 => "bar" 
DigitalRoss
la source
1
C'est assez intéressant ... que fait exactement la deuxième chaîne .new()?
Andrew
3
@Andrew: Struct.newcrée une nouvelle classe basée sur les clés de hachage, puis la seconde newcrée le premier objet de la classe qui vient d'être créée, en l'initialisant aux valeurs du Hash. Voir ruby-doc.org/core-1.8.7/classes/Struct.html
DigitalRoss
2
C'est en fait une très bonne façon de le faire car c'est à peu près ce pour quoi Struct est fait.
Chuck
2
Ou utilisez OpenStruct . require 'ostruct'; h = {:foo => 'foo'}; o = OpenStruct.new(h); o.foo == 'foo'
Justin Force
J'ai dû mapper mes clés sur des symboles:Struct.new(*hash.keys.map { |str| str.to_sym }).new(*hash.values)
erran
7

Tu nous donne envie de pleurer :)

Dans tous les cas, voir Object#instance_variable_getet Object#instance_variable_set.

Bon codage.


la source
euh oui, je ne pouvais pas m'empêcher de me demander ... pourquoi? quel serait le bon moment pour l'utiliser?
Zach Smith
par exemple, je pourrais vouloir avoir un set_entityrappel générique pour tous les contrôleurs, et je ne veux pas interférer avec les variables d'instance existantesdef set_entity(name, model); instance_variable_set(name, model.find_by(params[:id])); end;
user1201917
5

Vous pouvez également utiliser sendce qui empêche l'utilisateur de définir des variables d'instance inexistantes:

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

À utiliser sendlorsque dans votre classe il y a un setter comme attr_accessorpour vos variables d'instance:

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