【问题标题】:Ruby templates: How to pass variables into inlined ERB?Ruby 模板:如何将变量传递到内联 ERB?
【发布时间】:2010-11-23 06:18:24
【问题描述】:

我有一个内嵌到 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)

我该如何解决这个问题?

【问题讨论】:

    标签: ruby templates syntax erb


    【解决方案1】:

    对于一个简单的解决方案,使用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=&lt;%= x %&gt;, y=&lt;%= y %&gt;".erb(x: 1, y: 2) 的内容。

    【讨论】:

    • 你测试过这个吗?在我的系统上,您的精确代码会产生“NameError: undefined local variable or method 'name' for main:Object”。 (编辑:似乎是 1.9.2 问题stackoverflow.com/questions/3242470/…
    • @Ryan。确实,我只测试了 1.8.7,更新了。我会在您链接的问题中添加一个答案,我认为instance_eval 是最简单的解决方案。感谢您指出问题。
    • namespace.instance_eval { binding } 有什么作用?
    • 我正在阅读 instance_eval 所做的事情,但我只是不知道在 namespace 的上下文中执行绑定意味着什么。来自文档:Evaluates a string containing Ruby source code, or the given block, within the context of the receiver (obj). In order to set the context, the variable self is set to obj while the code is executing, giving the code access to obj’s instance variables.
    【解决方案2】:

    使用Binding的简单解决方案:

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

    【讨论】:

    • local_variable_set 是在 ruby​​ 2.1 中引入的。
    【解决方案3】:

    知道了!

    我创建了一个绑定类

    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 %>
    

    【讨论】:

    • 这是不必要的。在原始问题的代码中,只需将“result = template.result”替换为“result = template.result(binding)”,这将使用每个块的上下文而不是顶级上下文。
    【解决方案4】:

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

    result = template.result
    

    result = template.result(binding)
    

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

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

    【讨论】:

      【解决方案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
      

      参考:http://stoneship.org/essays/erb-and-the-context-object/

      【讨论】:

        【解决方案6】:

        我不能很好地回答为什么会发生这种情况,因为我不能 100% 确定 ERB 是如何工作的,但只看ERB RDocs,它说你需要一个binding是“用于设置代码评估上下文的 Binding 或 Proc 对象”。

        再次尝试上面的代码并替换

        result = template.result
        

        result = template.result(binding)
        

        成功了。

        我确定/希望有人会跳到这里并提供更详细的解释。干杯。

        编辑:有关Binding 的更多信息并使所有这些更清晰(至少对我而言),请查看Binding RDoc

        【讨论】:

          【解决方案7】:

          正如其他人所说,要使用一组变量评估 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​​ 来实现同样的效果

          【讨论】:

            【解决方案8】:

            也许最干净的解决方案是将特定的 current 局部变量传递给 erb 模板,而不是传递整个 binding。可以使用ERB#result_with_hash 方法(在 Ruby 2.5 中引入)

            DATA.keys.each do |current|
              result = template.result_with_hash(current: current)
            ...
            

            【讨论】:

              【解决方案9】:

              编辑:这是一个肮脏的解决方法。请看我的其他回答。

              这很奇怪,但是添加

              current = ""
              

              在“for-each”循环解决问题之前。

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

              【讨论】:

              • 我认为这是因为块参数在 Ruby 1.8 中不是真正的绑定变量。这在 Ruby 1.9 中发生了变化。
              • ERB 用来评估变量的默认绑定是顶级绑定。您的变量“current”不存在于顶级绑定中,除非您先在那里使用它(为其赋值)。
              【解决方案10】:

              【讨论】:

                猜你喜欢
                • 2015-12-31
                • 2012-09-01
                • 1970-01-01
                • 1970-01-01
                • 2016-05-28
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                相关资源
                最近更新 更多