Metaprogramação Ruby: nomes de variáveis ​​de instância dinâmicas

94

Digamos que eu tenha o seguinte hash:

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

Como eu poderia definir dinamicamente as chaves e valores para se tornarem variáveis ​​de instância em um objeto ...

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

... para que eu acabe com o seguinte dentro do modelo ...

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

?

Andrew
fonte

Respostas:

168

O método que você está procurando é instance_variable_set. Assim:

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

Ou, mais resumidamente,

hash.each &method(:instance_variable_set)

Se os nomes de suas variáveis ​​de instância estão faltando o "@" (como estão no exemplo do OP), você precisará adicioná-los, então seria mais como:

hash.each { |name, value| instance_variable_set("@#{name}", value) }
Mandril
fonte
18
Não funcionou para mim no 1.9.3. Em vez disso, useihash.each {|k,v| instance_variable_set("@#{k}",v)}
Andrei
3
mais uma razão para amar Ruby
jschorr
você pode explicar como hash.each &method(:instance_variable_set)o método instance_variable_setrecebe os dois parâmetros de que precisa?
Arnold Roa
alguma ideia de como fazer isso recursivamente? (se houver vários níveis no hash de entrada)
nemenems
13
h = { :foo => 'bar', :baz => 'qux' }

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

o.baz
 => "qux" 
o.foo
 => "bar" 
DigitalRoss
fonte
1
Isso é muito interessante ... o que exatamente o segundo acorrentado está .new()fazendo?
Andrew
3
@Andrew: Struct.newcria uma nova classe baseada nas chaves hash, e então a segunda newfaz o primeiro objeto da classe recém-criada, inicializando-o com os valores do Hash. Consulte ruby-doc.org/core-1.8.7/classes/Struct.html
DigitalRoss
2
Esta é realmente uma ótima maneira de fazer isso, pois é basicamente para isso que o Struct foi feito.
Chuck
2
Ou use OpenStruct . require 'ostruct'; h = {:foo => 'foo'}; o = OpenStruct.new(h); o.foo == 'foo'
Justin Force
Tive que mapear minhas chaves para símbolos:Struct.new(*hash.keys.map { |str| str.to_sym }).new(*hash.values)
erran
7

Você dá vontade de chorar :)

Em qualquer caso, veja Object#instance_variable_gete Object#instance_variable_set.

Boa codificação.


fonte
er sim, não pude deixar de me perguntar ... por quê? quando seria um bom momento para usar isso?
Zach Smith
por exemplo, posso querer ter um set_entityretorno de chamada genérico para todos os controladores e não quero interferir nas variáveis ​​de instância existentesdef set_entity(name, model); instance_variable_set(name, model.find_by(params[:id])); end;
user1201917
5

Você também pode usar o sendque evita que o usuário defina variáveis ​​de instância inexistentes:

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

Use sendquando em sua classe houver um setter como attr_accessorpara suas variáveis ​​de instância:

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