Come creare dynamicmente una variabile locale?

Ho una variabile var = "some_name" e vorrei creare un nuovo object e assegnarlo a some_name . Come posso farlo? Per esempio

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

Non è ansible creare dynamicmente variabili locali in Ruby 1.9+ (in Ruby 1.8 tramite eval ):

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

Possono essere utilizzati all’interno del codice stesso valutato, tuttavia:

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

Ruby 2.1 ha aggiunto local_variable_set , ma non può creare nuove variabili locali:

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

Questo comportamento non può essere modificato senza modificare Ruby stesso. L’alternativa è considerare la possibilità di archiviare i dati all’interno di un’altra struttura di dati, ad esempio un hash, invece di molte variabili locali:

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

Si noti che sia eval che local_variable_set consentono di riassegnare una variabile locale esistente:

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

Parlando di ruby ​​2.2.x, è vero che non è ansible creare variabili locali in modo programmatico nel contesto / binding corrente … ma è ansible impostare variabili in un particolare binding di cui si ha un handle.

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

È interessante notare che le chiamate al binding ti danno ogni volta una nuova rilegatura. Quindi è necessario ottenere un handle del binding a cui sei interessato e quindi valutare nel suo contesto una volta impostate le variabili desiderate.

Come è utile? Per esempio voglio valutare ERB e scrivere ERB è molto più bello se puoi usare < %= myvar %> invece di < %= opts[:myvar] %> o qualcosa del genere.

Per creare un nuovo bind sto usando un metodo di class del modulo (sono sicuro che qualcuno mi correggerà come chiamarlo correttamente, in java lo definirei un metodo statico) per ottenere un binding pulito con set di variabili particolari:

 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) 

Ora hai un legame con solo le variabili desiderate. Puoi usarlo per una valutazione più controllata di ERB o altro codice attendibile (eventualmente di terze parti) (questa non è una sandbox di alcun tipo). È come definire un’interfaccia.

aggiornamento: alcune note aggiuntive sui binding. Il luogo in cui vengono creati influisce anche sulla disponibilità dei metodi e sulla risoluzione delle costanti. Nell’esempio precedente creo un legame ragionevolmente pulito. Ma se voglio rendere disponibili i metodi di istanza di qualche object, potrei creare un’associazione con un metodo simile ma all’interno della class di quell’object. per esempio

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

Ora my_object.not_so_clean_binding consentirà al codice di chiamare my_object sull’object my_object . Allo stesso modo, puoi chiamare ad esempio SomeOtherClass.new in codice usando questa associazione invece di MyRootModule::SomeOtherClass.new . Quindi, a volte, quando si crea un legame sono necessarie più considerazioni rispetto alle sole variabili locali. HTH

Sebbene, come altri hanno sottolineato, non è ansible creare dynamicmente variabili locali in Ruby, è ansible simulare questo comportamento in una certa misura usando i metodi:

 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 

stampe:

 Value 1 Value 2 VALUE 2 

Alcune librerie combinano questa tecnica con instance_exec per esporre quelle che sembrano essere variabili locali all’interno di un blocco:

 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 

Stampe: 3

Sappi però che l’astrazione non è affatto perfetta:

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

Risultati in: undefined method `+' for nil:NilClass . Questo perché a= definisce una variabile locale attuale, inizializzata a nil , che ha la precedenza sul metodo a . Quindi viene chiamato a.+(1) e nil non ha un metodo + , quindi viene generato un errore.

Quindi, mentre questo metodo è piuttosto utile per simulare variabili locali di sola lettura, non sempre funziona bene quando si tenta di riassegnare la variabile all’interno del blocco.

È vero quello che altri hanno scritto che non è ansible dichiarare dynamicmente la variabile vera in un contesto locale. Tuttavia è ansible ottenere funzionalità simili con gli attributi dell’object e poiché nel mondo Ruby tutto è un object (anche il contesto principale) è ansible estendere facilmente tali oggetti con nuovi attributi. Certo, questa operazione può essere eseguita dynamicmente. Esaminiamo questo approccio.

In primo luogo, esaminiamo lo scope principale con irb .

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

Come puoi vedere ora, main è veramente un object. Gli oggetti possono avere attributi che hanno la stessa proprietà di riferimento indiretto come variabili. Normalmente, quando dichiariamo una nuova class attr_accessor metodo attr_accessor , ma main è già un object istanziato, quindi non possiamo dichiarare direttamente nuovi attributi. Qui i mixin di moduli vengono per il salvataggio.

 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] 

Ora vedi che l’object main stato macchiato con un nuovo modulo che ha introdotto un nuovo attributo foo . Per ulteriori controlli è ansible eseguire i methods per vedere che il main ora ha altri due metodi foo e foo= .

Per semplificare questa operazione ho scritto metaxa gem che ti consiglio vivamente di dare un’occhiata. Questo è un esempio di come usarlo.

 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