Metaprogrammazione Ruby: nomi di variabili di istanza dinamiche


94

Diciamo che ho il seguente hash:

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

Come potrei impostare dinamicamente le chiavi e i valori per diventare variabili di istanza in un oggetto ...

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

... quindi finisco con il seguente all'interno del modello ...

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

?

Risposte:


168

Il metodo che stai cercando è instance_variable_set. Così:

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

O, più brevemente,

hash.each &method(:instance_variable_set)

Se ai nomi delle variabili di istanza manca la "@" (come nell'esempio dell'OP), dovrai aggiungerli, quindi sarebbe più simile a:

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

18
Non ha funzionato per me per 1.9.3. Ho usato questo invecehash.each {|k,v| instance_variable_set("@#{k}",v)}
Andrei il

3
ancora un altro motivo per amare Ruby
jschorr

puoi spiegarci come hash.each &method(:instance_variable_set)il metodo instance_variable_setriceve i due parametri di cui ha bisogno?
Arnold Roa

qualche idea su come farlo in modo ricorsivo? (se sono presenti più livelli nell'hash di input)
nemenems

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

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

o.baz
 => "qux" 
o.foo
 => "bar" 

1
È piuttosto interessante ... cosa sta .new()facendo esattamente il secondo incatenato ?
Andrew

3
@Andrew: Struct.newcrea una nuova classe basata sulle chiavi hash, quindi la seconda newcrea il primo oggetto della classe appena creata, inizializzandolo ai valori dell'Hash. See ruby-doc.org/core-1.8.7/classes/Struct.html
DigitalRoss

2
Questo è davvero un ottimo modo per farlo poiché questo è praticamente ciò per cui Struct è fatto.
Chuck

2
Oppure usa OpenStruct . require 'ostruct'; h = {:foo => 'foo'}; o = OpenStruct.new(h); o.foo == 'foo'
Justin Force

Ho dovuto mappare le mie chiavi sui simboli:Struct.new(*hash.keys.map { |str| str.to_sym }).new(*hash.values)
erran

7

Ci fai venire voglia di piangere :)

In ogni caso, vedi Object#instance_variable_gete Object#instance_variable_set.

Buona codifica.


ehm si, non ho potuto fare a meno di chiedermi ... perché? quando sarebbe un buon momento per usarlo?
Zach Smith

ad esempio, potrei voler avere un set_entitycallback generico per tutti i controller e non voglio interferire con le variabili di istanza esistentidef set_entity(name, model); instance_variable_set(name, model.find_by(params[:id])); end;
user1201917

5

Puoi anche usare sendche impedisce all'utente di impostare variabili di istanza inesistenti:

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

Usalo sendquando nella tua classe c'è un setter come attr_accessorper le tue variabili di istanza:

class Example
  attr_accessor :foo, :baz
  def initialize(hash)
    hash.each { |key, value| send("#{key}=", value) }
  end
end
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.