Ruby templates: how to pass variables to embedded ERB?
I have an ERB template built into Ruby code:
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 I cannot pass the variable "current" to the template.
Mistake:
(erb):1: undefined local variable or method `current' for main:Object (NameError) How to fix it?
Got it!
I am creating a binding class
class BindMe def initialize(key,val) @key=key @val=val end def get_binding return binding() end end and transfer the instance to 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 The .erb template file looks like this:
Key: <%= @key %> For a simple solution, 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 The above code is quite simple, but it has (at least) two problems: 1) Since it relies on OpenStruct , access to a nonexistent variable returns nil , while you probably prefer it with noise, 2) binding is called inside block, that it is in closure, so it includes all local variables in scope (in fact, these variables will obscure the attributes of struct!).
So here is another solution, more detailed, but without any of these problems:
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 Of course, if you intend to use this often, make sure you create a String#erb that allows you to write something like "x=<%= x %>, y=<%= y %>".erb(x: 1, y: 2) .
A simple solution using Binding :
b = binding b.local_variable_set(:a, 'a') b.local_variable_set(:b, 'b') ERB.new(template).result(b) In the source question code, just replace
result = template.result from
result = template.result(binding) This will use the context of each block, not the top-level context.
(Just extracted @sciurus's comment as an answer, because it is the shortest and most correct.)
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/
I cannot give you a very good answer as to why this is happening, because I am not 100% sure how ERB works, but just looking at ERB RDocs , it says that you need binding , which is a "Binding or Proc object which is used to set the context for code evaluation. "
Try the above code again and just replace
result = template.result from
result = template.result(binding) made it work.
I am sure / hope someone will drop by here and provide a more detailed explanation of what is happening. Greetings.
UPDATE: For more information on Binding and to make it all a little clearer (at least for me), see Binding RDoc .
EDIT . This is a dirty workaround. See my other answer.
This is totally strange, but adding
current = "" before the for-each loop fixes the problem.
God bless script languages ββand their "language features" ...
This article perfectly explains this.
http://www.garethrees.co.uk/2014/01/12/create-a-template-rendering-class-with-erb/
As others have said, to evaluate an ERB with some set of variables, you need the correct binding. There are some solutions with the definition of classes and methods, but I think that the simplest and most effective and safe way is to create a clean binding and use it to analyze ERB. Here is my hit on it (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) I think with eval and without ** the same thing can be done with an older ruby ββthan 2.1