Ruby Metaprogramming: Dynamische Instanzvariablennamen

94

Angenommen, ich habe den folgenden Hash:

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

Wie könnte ich die Schlüssel und Werte dynamisch so einstellen, dass sie zu Instanzvariablen in einem Objekt werden ...

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

... so dass ich im Modell folgendes habe ...

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

?

Andrew
quelle

Antworten:

168

Die Methode, die Sie suchen, ist instance_variable_set. So:

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

Oder kurz gesagt:

hash.each &method(:instance_variable_set)

Wenn in Ihren Instanzvariablennamen das "@" fehlt (wie im Beispiel des OP), müssen Sie sie hinzufügen, sodass es eher so aussieht:

hash.each { |name, value| instance_variable_set("@#{name}", value) }
Futter
quelle
18
Hat bei mir für 1.9.3 nicht funktioniert. Ich habe dies stattdessen verwendethash.each {|k,v| instance_variable_set("@#{k}",v)}
Andrei
3
noch ein Grund, Ruby zu lieben
jschorr
Können Sie bitte erklären, wie hash.each &method(:instance_variable_set)die Methode instance_variable_setdie beiden benötigten Parameter empfängt?
Arnold Roa
Irgendeine Idee, wie man das rekursiv macht? (wenn es mehrere Ebenen im Eingabe-Hash gibt)
nemenems
13
h = { :foo => 'bar', :baz => 'qux' }

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

o.baz
 => "qux" 
o.foo
 => "bar" 
DigitalRoss
quelle
1
Das ist ziemlich interessant ... was genau macht die zweite Kette .new()?
Andrew
3
@ Andrew: Erstellt Struct.neweine neue Klasse basierend auf den Hash-Schlüsseln. Die zweite Klasse erstellt dann das newerste Objekt der gerade erstellten Klasse und initialisiert es mit den Werten des Hash. Siehe ruby-doc.org/core-1.8.7/classes/Struct.html
DigitalRoss
2
Dies ist eine wirklich großartige Möglichkeit, da Struct so ziemlich genau dafür gemacht ist.
Chuck
2
Oder verwenden Sie OpenStruct . require 'ostruct'; h = {:foo => 'foo'}; o = OpenStruct.new(h); o.foo == 'foo'
Justin Force
Ich musste meine Schlüssel Symbolen Struct.new(*hash.keys.map { |str| str.to_sym }).new(*hash.values)
zuordnen
7

Du bringst uns dazu zu weinen :)

In jedem Fall siehe Object#instance_variable_getund Object#instance_variable_set.

Viel Spaß beim Codieren.


quelle
äh ja, ich musste mich fragen ... warum? Wann wäre ein guter Zeitpunkt, dies zu nutzen?
Zach Smith
Beispielsweise möchte ich möglicherweise einen generischen set_entityRückruf für alle Controller haben und vorhandene Instanzvariablen nicht störendef set_entity(name, model); instance_variable_set(name, model.find_by(params[:id])); end;
user1201917,
5

Sie können auch Folgendes verwenden send, um zu verhindern, dass der Benutzer nicht vorhandene Instanzvariablen festlegt:

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

Verwenden sendSie diese Option, wenn in Ihrer Klasse ein Setter wie attr_accessorfür Ihre Instanzvariablen vorhanden ist:

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