Modelli di rubini: come passare variabili in ERB in linea?

Ho un modello ERB inline nel codice Ruby:

require 'erb' DATA = { :a => "HELLO", :b => "WORLD", } template = ERB.new <<-EOF current key is:  current value is:  EOF DATA.keys.each do |current| result = template.result outputFile = File.new(current.to_s,File::CREAT|File::TRUNC|File::RDWR) outputFile.write(result) outputFile.close end 

Non riesco a passare la variabile “corrente” nel modello.

L’errore è:

 (erb):1: undefined local variable or method `current' for main:Object (NameError) 

Come posso risolvere questo?

Per una soluzione semplice, utilizzare OpenStruct :

 require 'erb' require 'ostruct' namespace = OpenStruct.new(name: 'Joan', last: 'Maragall') template = 'Name: <%= name %> <%= last %>' result = ERB.new(template).result(namespace.instance_eval { binding }) #=> Name: Joan Maragall 

Il codice sopra è abbastanza semplice ma ha (almeno) due problemi: 1) Poiché si basa su OpenStruct , un accesso a una variabile non esistente restituisce nil mentre probabilmente preferiresti che fallisse rumorosamente. 2) il binding è chiamato all’interno di un blocco, cioè in una chiusura, quindi include tutte le variabili locali nello scope (infatti, queste variabili ombreggiano gli attributi della struct!).

Quindi ecco un’altra soluzione, più prolissa ma senza nessuno di questi problemi:

 class Namespace def initialize(hash) hash.each do |key, value| singleton_class.send(:define_method, key) { value } end end def get_binding binding end end template = 'Name: <%= name %> <%= last %>' ns = Namespace.new(name: 'Joan', last: 'Maragall') ERB.new(template).result(ns.get_binding) #=> Name: Joan Maragall 

Naturalmente, se lo utilizzerai spesso, assicurati di creare un’estensione String#erb che ti permetta di scrivere qualcosa come "x=<%= x %>, y=<%= y %>".erb(x: 1, y: 2) .

Soluzione semplice usando Binding :

 b = binding b.local_variable_set(:a, 'a') b.local_variable_set(:b, 'b') ERB.new(template).result(b) 

Fatto!

Creo una class di binding

 class BindMe def initialize(key,val) @key=key @val=val end def get_binding return binding() end end 

e passare un’istanza a ERB

 dataHash.keys.each do |current| key = current.to_s val = dataHash[key] # here, I pass the bindings instance to ERB bindMe = BindMe.new(key,val) result = template.result(bindMe.get_binding) # unnecessary code goes here end 

Il file modello .erb ha il seguente aspetto:

 Key: <%= @key %> 

Nel codice della domanda originale, basta sostituire

 result = template.result 

con

 result = template.result(binding) 

Questo utilizzerà il contesto di ciascun blocco piuttosto che il contesto di primo livello.

(Ho appena estratto il commento da @sciurus come risposta perché è il più breve e corretto.)

 require 'erb' class ERBContext def initialize(hash) hash.each_pair do |key, value| instance_variable_set('@' + key.to_s, value) end end def get_binding binding end end class String def erb(assigns={}) ERB.new(self).result(ERBContext.new(assigns).get_binding) end end 

REF: http://stoneship.org/essays/erb-and-the-context-object/

Non posso darti una risposta molto buona sul motivo per cui ciò sta accadendo perché non sono sicuro al 100% di come funzioni ERB, ma osservando l’ RDoc ERB , si dice che è necessario un binding che sia a Binding or Proc object which is used to set the context of code evaluation. Provando di nuovo il codice precedente e sostituendo semplicemente result = template.result con result = template.result(binding) ha funzionato.

Sono sicuro / spero che qualcuno salti qui e fornisca una spiegazione più dettagliata di quello che sta succedendo. Saluti.

EDIT: per ulteriori informazioni su Binding e rendendo tutto questo un po ‘più chiaro (almeno per me), controlla l’ RDoc Binding .

EDIT : Questa è una soluzione sporca. Si prega di vedere la mia altra risposta.

È totalmente strano, ma aggiungendo

 current = "" 

prima che il ciclo “for-each” risolva il problema.

Dio benedica i linguaggi di scripting e le loro “caratteristiche linguistiche” …

Come altri hanno detto, per valutare ERB con un certo insieme di variabili, è necessario un corretto binding. Ci sono alcune soluzioni con la definizione di classi e metodi, ma penso che la cosa più semplice e che dia più controllo e sicurezza sia generare un binding pulito e usarlo per analizzare l’ERB. Ecco la mia opinione su di esso (ruby 2.2.x):

 module B 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 = B.binding_from_hash(a: 5, **other_opts) result = ERB.new(template).result(my_nice_binding) 

Penso che con eval e senza ** stesso possa essere fatto lavorando con ruby ​​più vecchio di 2.1