Metaprogramowanie w Rubim: dynamiczne nazwy zmiennych instancji

95

Powiedzmy, że mam następujący hash:

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

Jak mogę dynamicznie ustawić klucze i wartości, aby stały się zmiennymi instancji w obiekcie ...

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

... tak żebym miał w modelu następujące elementy ...

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

?

Andrzej
źródło

Odpowiedzi:

169

Metoda, której szukasz, to instance_variable_set. Więc:

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

Lub, mówiąc krócej,

hash.each &method(:instance_variable_set)

Jeśli w nazwach zmiennych instancji brakuje „@” (tak jak w przykładzie OP), musisz je dodać, więc wyglądałoby to bardziej jak:

hash.each { |name, value| instance_variable_set("@#{name}", value) }
Gdakanie
źródło
18
Nie działało dla mnie w 1.9.3. Użyłem tego zamiast tegohash.each {|k,v| instance_variable_set("@#{k}",v)}
Andrei
3
kolejny powód, by pokochać Ruby
jschorr
czy możesz wyjaśnić, w jaki hash.each &method(:instance_variable_set)sposób metoda instance_variable_setotrzymuje dwa parametry, których potrzebuje?
Arnold Roa
masz pomysł, jak to zrobić rekurencyjnie? (jeśli w haszu wejściowym jest wiele poziomów)
nemenems
13
h = { :foo => 'bar', :baz => 'qux' }

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

o.baz
 => "qux" 
o.foo
 => "bar" 
DigitalRoss
źródło
1
To całkiem interesujące ... co dokładnie .new()robi drugi łańcuch ?
Andrew,
3
@Andrew: Struct.newtworzy nową klasę w oparciu o klucze mieszające, a następnie druga newtworzy pierwszy obiekt właśnie utworzonej klasy, inicjując ją do wartości Hash. Zobacz ruby-doc.org/core-1.8.7/classes/Struct.html
DigitalRoss,
2
W rzeczywistości jest to naprawdę świetny sposób, aby to zrobić, ponieważ do tego jest stworzony Struct.
Chuck,
2
Lub użyj OpenStruct . require 'ostruct'; h = {:foo => 'foo'}; o = OpenStruct.new(h); o.foo == 'foo'
Justin Force,
Musiałem odwzorować klucze na symbole:Struct.new(*hash.keys.map { |str| str.to_sym }).new(*hash.values)
erran
7

Sprawiasz, że chcemy płakać :)

W każdym razie zobacz Object#instance_variable_geti Object#instance_variable_set.

Miłego kodowania.


źródło
eee tak, nie mogłem przestać się zastanawiać ... dlaczego? kiedy byłby dobry moment, aby to wykorzystać?
Zach Smith
na przykład chciałbym mieć ogólne set_entitywywołanie zwrotne dla wszystkich kontrolerów i nie chcę ingerować w istniejące zmienne instancjidef set_entity(name, model); instance_variable_set(name, model.find_by(params[:id])); end;
user1201917
5

Możesz także użyć, sendktóry zapobiega ustawianiu przez użytkownika nieistniejących zmiennych instancji:

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

Użyj, sendgdy w twojej klasie jest seter, taki jak attr_accessordla zmiennych instancji:

class Example
  attr_accessor :foo, :baz
  def initialize(hash)
    hash.each { |key, value| send("#{key}=", value) }
  end
end
Asarluhi
źródło