2009-08-27 11 views
50

Tengo una plantilla de ERB inline en código Ruby:Plantillas de Ruby: ¿cómo pasar variables a ERB en línea?

require 'erb' 

DATA = { 
    :a => "HELLO", 
    :b => "WORLD", 
} 

template = ERB.new <<-EOF 
    current key is: <%= current %> 
    current value is: <%= DATA[current] %> 
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 

No puedo pasar la variable "actual" en la plantilla.

El error es:

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

¿Cómo puedo solucionar esto?

Respuesta

10

¡Gracias!

puedo crear una clase fijaciones

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

y pasar una instancia de 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 

El archivo de plantilla .erb se ve así:

Key: <%= @key %> 
+8

Esto no es necesario. En el código de su pregunta original, simplemente reemplace "result = template.result" por "result = template.result (binding)" que usará el contexto de cada bloque en lugar del contexto de nivel superior. – sciurus

4

No puedo dar una muy buena respuesta de por qué esto está ocurriendo porque no estoy 100% seguro de cómo funciona ERB, pero sólo mirar el ERB RDocs, se dice que se necesita un binding que es a Binding or Proc object which is used to set the context of code evaluation. Tratando su código anterior de nuevo y simplemente reemplazando result = template.result con result = template.result(binding) lo hizo funcionar.

Estoy seguro/espero que alguien salte aquí y brinde una explicación más detallada de lo que está sucediendo. Aclamaciones.

EDITAR: Para obtener más información sobre Binding y hacer todo esto un poco más claro (al menos para mí), echa un vistazo a Binding RDoc.

0

EDIT: Esta es una solución sucia. Por favor mira mi otra respuesta.

Es totalmente extraño, pero la adición de

current = "" 

antes de la "para-cada" bucle soluciona el problema.

Dios bendiga a los lenguajes de script y sus "características del lenguaje" ...

+0

Creo que esto se debe a que los parámetros del bloque no están obligados real de las variables en Ruby 1.8. Esto ha cambiado en Ruby 1.9. –

+1

El enlace predeterminado que utiliza ERB para evaluar las variables es el enlace de nivel superior. Su variable "actual" no existe en el enlace de nivel superior, a menos que la use allí primero (le asigne un valor). – molf

+0

Entonces, en Ruby 1.9 no funcionará? –

57

Para una solución simple, use 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 

El código anterior es bastante simple pero tiene (al menos) dos problemas: 1) Dado que depende de OpenStruct, un acceso a una variable no existente devuelve nil mientras que probablemente prefiera que haya fallado ruidosamente. 2) binding se llama dentro de un bloque, eso es todo, en un cierre, por lo que incluye todas las variables locales en el alcance (de hecho, estas variables sombrearán los atributos de la estructura!).

Así que aquí es otra solución, más prolija, pero sin que ninguno de estos problemas:

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 

Por supuesto, si usted va a utilizar esto a menudo, asegúrese de crear una extensión String#erb que le permite escribir algo así como "x=<%= x %>, y=<%= y %>".erb(x: 1, y: 2).

+0

¿Has probado esto? En mi sistema, su código exacto produce "NameError: variable local indefinida o método' name 'para main: Object ". (Editar: parece ser un problema de 1.9.2 http://stackoverflow.com/questions/3242470/problem-using-openstruct-with-erb) –

+0

@Ryan. De hecho, lo probé solo 1.8.7, actualizado. Añadiré una respuesta en la pregunta que vincula, creo que 'instancia_eval' es la solución más fácil. Gracias por resolver el problema. – tokland

+0

@Ryan, agregó una nueva solución en http://stackoverflow.com/a/8293786/188031. – tokland

5
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: solución http://stoneship.org/essays/erb-and-the-context-object/

19

simple usando Binding:

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

' local_variable_set' se introdujo en ruby ​​2.1. – kbrock

0

Como otros han dicho, para evaluar ERB con un conjunto de variables, se necesita una unión adecuada. Hay algunas soluciones para definir clases y métodos, pero creo que lo más simple y que da más control y lo más seguro es generar un enlace limpio y usarlo para analizar el ERB. Aquí está mi opinión sobre ella (2.2.x rubí):

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) 

Creo que con eval y sin ** mismo se puede hacer trabajando con el rubí mayores de 2.1

5

En el código de la pregunta original, basta con sustituir

result = template.result 

con

result = template.result(binding) 

que utilizará º El contexto de cada bloque en lugar del contexto de nivel superior.

(acaba de extraer el comentario de @sciurus como respuesta porque es la más corta y la más correcta.)

Cuestiones relacionadas