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'

?

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

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" 

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.


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
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.