Ruby templates: How to pass variables into inlined ERB?

60,689

Solution 1

Got it!

I create a bindings class

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

and pass an 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 %>

Solution 2

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 code above is simple enough but has (at least) two problems: 1) Since it relies on OpenStruct, an access to a non-existing variable returns nil while you'd probably prefer that it failed noisily. 2) binding is called within a block, that's it, in a closure, so it includes all the local variables in the scope (in fact, these variables will shadow the attributes of the struct!).

So here is another solution, more verbose 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 are going to use this often, make sure you create a String#erb extension that allows you to write something like "x=<%= x %>, y=<%= y %>".erb(x: 1, y: 2).

Solution 3

Simple solution using Binding:

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

Solution 4

In the code from original question, just replace

result = template.result

with

result = template.result(binding)

That will use the each block's context rather than the top-level context.

(Just extracted the comment by @sciurus as answer because it's the shortest and most correct one.)

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

Share:
60,689
ivan_ivanovich_ivanoff
Author by

ivan_ivanovich_ivanoff

!!! Netbeans 6.7 RELEASED !!! C is far better and easier than Java! Why? It is easier to use void pointers and to do pointer arithmetic, than explaining the basic concepts of OOP to a C programmer. Joke of the century: PHP is good, because it works... "PHP programming" is an oxymoron. There is no PHP programming. There is only PHP scriptkidding. There are two types of people who use PHP: - those who don't know other languages, - and those who HAVE TO use it Java is to JavaScript what Car is to Carpet. The LHC is the wrong answer to the technological singularity.

Updated on July 09, 2022

Comments

  • ivan_ivanovich_ivanoff
    ivan_ivanovich_ivanoff almost 2 years

    I have an ERB template inlined 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 can't pass the variable "current" into the template.

    The error is:

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

    How do I fix this?