Como criar dinamicamente uma variável local?

Eu tenho uma variável var = "some_name" e gostaria de criar um novo object e atribuí-lo a some_name . Como eu posso fazer isso? Por exemplo

 var = "some_name" some_name = Struct.new(:name) # I need this a = some_name.new('blah') # so that I can do this. 

Você não pode criar dinamicamente variables ​​locais no Ruby 1.9+ (você poderia no Ruby 1.8 via eval ):

 eval 'foo = "bar"' foo # NameError: undefined local variable or method `foo' for main:Object 

Eles podem ser usados ​​dentro do próprio código de avaliação, embora:

 eval 'foo = "bar"; foo + "baz"' #=> "barbaz" 

Ruby 2.1 adicionou local_variable_set , mas isso não pode criar novas variables ​​locais:

 binding.local_variable_set :foo, 'bar' foo # NameError: undefined local variable or method `foo' for main:Object 

Esse comportamento não pode ser alterado sem modificar o próprio Ruby. A alternativa é, em vez disso, considerar armazenar seus dados dentro de outra estrutura de dados, por exemplo, um hash, em vez de muitas variables ​​locais:

 hash = {} hash[:my_var] = :foo 

Observe que eval e local_variable_set permitem reatribuir uma variável local existente:

 foo = nil eval 'foo = "bar"' foo #=> "bar" binding.local_variable_set :foo, 'baz' foo #=> "baz" 

Falando de Ruby 2.2.x, é verdade que você não pode criar variables ​​locais programaticamente no contexto atual / binding .. mas você pode definir variables ​​em alguma binding particular da qual você tenha controle.

 b = binding b.local_variable_set :gaga, 5 b.eval "gaga" => 5 

Interessante aqui é que as chamadas para binding fornecem uma nova binding a cada vez. Então, você precisa obter um identificador da binding em que está interessado e, em seguida, eval em seu contexto, uma vez que as variables ​​desejadas estejam definidas.

Como isso é útil? Por exemplo, eu quero avaliar ERB e escrever ERB é muito melhor se você pode usar < %= myvar %> vez de < %= opts[:myvar] %> ou algo parecido.

Para criar uma nova binding, estou usando um método de class de módulo (tenho certeza que alguém irá me corrigir como chamar isso corretamente, em java eu ​​chamaria de método estático) para obter uma binding limpa com determinadas variables ​​definidas:

 module M def self.clean_binding binding end def self.binding_from_hash(**vars) b = self.clean_binding vars.each do |k, v| b.local_variable_set k.to_sym, v end return b end end my_nice_binding = M.binding_from_hash(a: 5, **other_opts) 

Agora você tem uma binding apenas com as variables ​​desejadas. Você pode usá-lo para uma melhor avaliação controlada do ERB ou outro código confiável (possivelmente de terceiros) (isso não é um sandbox de qualquer tipo). É como definir uma interface.

update: Algumas notas adicionais sobre ligações. O lugar para criá-los também afeta a disponibilidade de methods e a resolução de constantes. No exemplo acima, criei uma binding razoavelmente limpa. Mas se eu quiser disponibilizar os methods de instância de algum object, eu poderia criar uma binding por um método semelhante, mas dentro da class desse object. por exemplo

 module MyRooTModule class Work def my_instance_method ... end def not_so_clean_binding binding end end class SomeOtherClass end end 

Agora meu my_object.not_so_clean_binding permitirá que o código chame #my_instance_method no object my_object . Da mesma forma, você pode chamar por exemplo SomeOtherClass.new no código usando essa binding em vez de MyRootModule::SomeOtherClass.new . Portanto, às vezes, há mais consideração necessária ao criar uma binding do que apenas variables ​​locais. HTH

Embora, como outros apontaram, você não pode criar dinamicamente variables ​​locais em Ruby, você pode simular este comportamento em algum grau usando methods:

 hash_of_variables = {var1: "Value 1", var2: "Value 2"} hash_of_variables.each do |var, val| define_method(var) do instance_variable_get("@__#{var}") end instance_variable_set("@__#{var}", val) end puts var1 puts var2 var1 = var2.upcase puts var1 

Impressões:

 Value 1 Value 2 VALUE 2 

Algumas bibliotecas combinam essa técnica com instance_exec para expor o que parece ser variables ​​locais dentro de um bloco:

 def with_vars(vars_hash, &block) scope = Object.new vars_hash.each do |var, val| scope.send(:define_singleton_method, var) do scope.instance_variable_get("@__#{var}") end scope.instance_variable_set("@__#{var}", val) end scope.instance_exec(&block) end with_vars(a: 1, b:2) do puts a + b end 

Impressões: 3

Esteja ciente de que a abstração não é perfeita:

 with_vars(a: 1, b:2) do a = a + 1 puts a end 

Resultados em: undefined method `+' for nil:NilClass . Isso ocorre porque a= define uma variável local real, inicializada como nil , que tem precedência sobre o método a . Então a.+(1) é chamada, e nil não tem um método + , então um erro é lançado.

Portanto, embora esse método seja bastante útil para simular variables ​​locais somente leitura, nem sempre funciona bem quando você tenta reatribuir a variável dentro do bloco.

É verdade que os outros escreveram que você não pode declarar dinamicamente a variável verdadeira em um contexto local. No entanto, você pode obter uma funcionalidade semelhante com atributos de object e, como no mundo Ruby tudo é um object (mesmo contexto principal), é possível estender facilmente esses objects com novos atributos. De corse, esta operação pode ser feita dinamicamente. Vamos examinar essa abordagem.

Primeiramente, vamos olhar o escopo principal com o irb .

 > self => main > self.class => Object > self.class.ancestors => [Object, Kernel, BasicObject] 

Como você pode ver agora, main é verdadeiramente um object. Objetos podem ter atributos que possuem a mesma propriedade de indireto de variables. Normalmente, ao declarar uma nova class, attr_accessor método attr_accessor , mas main já é um object instanciado, portanto, não podemos declarar novos atributos diretamente. Aqui misturas de módulos vêm para resgate.

 variable_name = 'foo' variable_value = 'bar' variable_module = Module.new do attr_accessor variable_name.to_sym end include variable_module instance_variable_set("@#{variable_name}", variable_value) p foo # "bar" self.foo = 'bad' p foo # "baz" self.class.ancestors # [Object, #, Kernel, BasicObject] 

Agora você vê que o object main foi contaminado com o novo módulo que introduziu o novo atributo foo . Para uma inspeção mais detalhada, você pode executar methods para ver que o main agora tem mais dois methods foo e foo= .

Para simplificar esta operação eu escrevi metaxa gem que eu recomendo fortemente que você confira. Este é um exemplo de como usá-lo.

 require 'metaxa' include Metaxa introduce :foo, with_value: 'foo' puts foo == 'foo' # true puts foo === get(:foo) # true set :foo, 'foobar' puts foo == 'foobar' # true puts foo === get(:foo) # true self.foo = 'foobarbaz' puts foo == 'foobarbaz' # true puts foo === get(:foo) # true