代码之家  ›  专栏  ›  技术社区  ›  ivan_ivanovich_ivanoff

Ruby模板:如何将变量传递到内联erb中?

  •  53
  • ivan_ivanovich_ivanoff  · 技术社区  · 16 年前

    我有一个嵌入到Ruby代码中的erb模板:

    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
    

    我无法将变量“current”传递到模板中。

    错误是:

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

    我该怎么解决这个问题?

    9 回复  |  直到 9 年前
        1
  •  58
  •   tokland    10 年前

    对于简单的解决方案,请使用 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
    

    上面的代码足够简单,但至少有两个问题:1)因为它依赖于 OpenStruct ,对不存在的变量的访问返回 nil 虽然你可能更希望它失败得很大声。2) binding 在一个块中调用,也就是在一个闭包中调用,因此它包含了作用域中的所有局部变量(实际上,这些变量将隐藏结构的属性!).

    所以这里有另一个解决方案,更详细,但没有任何这些问题:

    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
    

    当然,如果您要经常使用它,请确保创建一个 String#erb 允许您编写类似 "x=<%= x %>, y=<%= y %>".erb(x: 1, y: 2) .

        2
  •  23
  •   asfer    10 年前

    使用简单的解决方案 Binding :

    b = binding
    b.local_variable_set(:a, 'a')
    b.local_variable_set(:b, 'b')
    ERB.new(template).result(b)
    
        3
  •  10
  •   ivan_ivanovich_ivanoff    16 年前

    知道了!

    我创建了一个绑定类

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

    并将实例传递给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
    

    .erb模板文件如下所示:

    Key: <%= @key %>
    
        4
  •  7
  •   geekQ Petter    9 年前

    在原始问题的代码中,只需替换

    result = template.result
    

    具有

    result = template.result(binding)
    

    它将使用每个块的上下文,而不是顶级上下文。

    (刚刚提取了@sciurus的评论作为答案,因为它是最短和最正确的。)

        5
  •  6
  •   alvin2ye    11 年前
    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
    

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

        6
  •  4
  •   theIV    16 年前

    我不能给你一个很好的答案来解释为什么会发生这种情况,因为我不完全确定erb是如何工作的,只是看看 ERB RDocs 它说你需要一个 binding 哪个是 a Binding or Proc object which is used to set the context of code evaluation. 再次尝试上述代码,只需替换 result = template.result 具有 result = template.result(binding) 让它起作用。

    我相信/希望有人会跳到这里,对发生的事情提供更详细的解释。干杯。

    编辑:有关 Binding 让这一切更清楚一点(至少对我来说),看看 Binding RDoc .

        7
  •  0
  •   ivan_ivanovich_ivanoff    16 年前

    编辑 :这是一个肮脏的解决方法。请看我的另一个答案。

    这很奇怪,但补充说

    current = ""
    

    在“for each”循环修复问题之前。

    上帝保佑脚本语言及其“语言特性”…

        8
  •  0
  •   Alex Levine    10 年前
        9
  •  0
  •   akostadinov    9 年前

    正如其他人所说,要用一组变量来评估erb,您需要一个适当的绑定。有一些定义类和方法的解决方案,但我认为最简单、最安全的方法是生成一个干净的绑定并使用它来解析erb。这是我的看法(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)
    

    我认为 eval 而没有 ** 使用比2.1更老的Ruby也可以做到这一点。