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'

?

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) }

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" 

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.


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
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.