Ruby templates: How to pass variables into inlined ERB?

前端 未结 10 1711
陌清茗
陌清茗 2020-11-30 21:18

I have an ERB template inlined into Ruby code:

require \'erb\'

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

template = ERB.new <<-EOF
          


        
相关标签:
10条回答
  • 2020-11-30 21:43

    Maybe the cleanest solution would be to pass specific current local variable to erb template instead of passing the entire binding. It's possible with ERB#result_with_hash method (introduced in Ruby 2.5)

    DATA.keys.each do |current|
      result = template.result_with_hash(current: current)
    ...
    
    0 讨论(0)
  • 2020-11-30 21:44

    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).

    0 讨论(0)
  • 2020-11-30 21:51

    EDIT: This is a dirty workaround. Please see my other answer.

    It's totally strange, but adding

    current = ""
    

    before the "for-each" loop fixes the problem.

    God bless scripting languages and their "language features"...

    0 讨论(0)
  • 2020-11-30 21:53

    Simple solution using Binding:

    b = binding
    b.local_variable_set(:a, 'a')
    b.local_variable_set(:b, 'b')
    ERB.new(template).result(b)
    
    0 讨论(0)
  • 2020-11-30 21:54
    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/

    0 讨论(0)
  • 2020-11-30 21:59

    I can't give you a very good answer as to why this is happening because I'm not 100% sure how ERB works, but just looking at the ERB RDocs, it says that you need a binding which is "a Binding or Proc object which is used to set the context of code evaluation".

    Trying your above code again and just replacing

    result = template.result
    

    with

    result = template.result(binding)
    

    made it work.

    I'm sure/hope someone will jump in here and provide a more detailed explanation of what's going on. Cheers.

    EDIT: For some more information on Binding and making all of this a little clearer (at least for me), check out the Binding RDoc.

    0 讨论(0)
提交回复
热议问题