【问题标题】:Render an ERB template with values from a hash使用哈希值呈现 ERB 模板
【发布时间】:2012-02-15 19:16:48
【问题描述】:

我必须在这里忽略一些非常简单的事情,但我似乎无法弄清楚如何使用哈希映射中的值呈现简单的 ERB 模板。

我对 ruby​​ 比较陌生,来自 python。我有一个 ERB 模板(不是 HTML),我需要使用从外部源接收的哈希映射中获取的上下文来呈现它。

然而,ERB 的文档指出ERB.result 方法采用binding。我了解到它们是在 ruby​​ 中保存变量上下文的东西(我想在 python 中类似于locals()globals()?)。但是,我不知道如何从我的哈希映射中构建一个绑定对象。

一点点(实际上是很多)谷歌搜索给了我这个:http://refactormycode.com/codes/281-given-a-hash-of-variables-render-an-erb-template,它使用了一些让我无法理解的 ruby​​ 元编程魔法。

那么,这个问题没有简单的解决方案吗?还是有更好的模板引擎(不绑定到 HTML)更适合这个? (我只选择了 ERB,因为它在标准库中)。

【问题讨论】:

  • 我不知道有任何“绑定”到 HTML 的 Ruby 模板引擎;模板就是模板。也不确定您链接到的解决方案有什么问题 - 是将哈希放入模块的问题吗?
  • 戴夫,这样没什么问题。只是我认为对于这样一个简单的问题,可能有比使用元编程更优雅的解决方案。

标签: ruby erb template-engine


【解决方案1】:
require 'erb'
require 'ostruct'

def render(template, vars)
  ERB.new(template).result(OpenStruct.new(vars).instance_eval { binding })
end

例如

render("Hey, <%= first_name %> <%= last_name %>", first_name: "James", last_name: "Moriarty")
# => "Hey, James Moriarty" 

更新:

一个没有 ERB 的简单例子:

def render(template, vars)
  eval template, OpenStruct.new(vars).instance_eval { binding }
end

例如

render '"Hey, #{first_name} #{last_name}"', first_name: "James", last_name: "Moriarty"
# => "Hey, James Moriarty

更新 2:查看下面的 @adam-spiers 评论。

【讨论】:

  • +1,这是一种从哈希创建绑定的超级优雅的方式。
  • 一开始看起来不错,但不幸的是,绑定还从 OpenStruct 实例化的上下文中继承了局部变量和方法。这使模板访问方式超出预期,并可能导致错误甚至安全漏洞,例如gist.github.com/aspiers/ad6549058ee423819976
【解决方案2】:

我不知道这是否符合“更优雅”的条件:

require 'erb'
require 'ostruct'

class ErbalT < OpenStruct
  def render(template)
    ERB.new(template).result(binding)
  end
end

et = ErbalT.new({ :first => 'Mislav', 'last' => 'Marohnic' })
puts et.render('Name: <%= first %> <%= last %>')

或者来自类方法:

class ErbalT < OpenStruct
  def self.render_from_hash(t, h)
    ErbalT.new(h).render(t)
  end

  def render(template)
    ERB.new(template).result(binding)
  end
end

template = 'Name: <%= first %> <%= last %>'
vars = { :first => 'Mislav', 'last' => 'Marohnic' }
puts ErbalT::render_from_hash(template, vars)

(ErbalT 有 Erb,T 作为模板,听起来像“凉茶”。命名很难。)

【讨论】:

  • 谢谢戴夫。是的,这个解决方案看起来更好一些,虽然我对OpenStruct 毫无头绪。我去看看文档,没问题:)
  • @ShrikantSharat 实际上或多或少是一回事,但它是执行元数据的操作系统。
【解决方案3】:

Ruby 2.5 有 ERB#result_with_hash 提供此功能:

$ ruby -rerb -e 'p ERB.new("Hi <%= name %>").result_with_hash(name: "Tom")'
"Hi Tom"

【讨论】:

  • 这个答案是最好的答案。最新的 ruby​​ 比使用绑定更容易。
  • 嗯……我仍然可以用它做坏事:ERB.new('Lol &lt;%= File.read("/etc/hosts") %&gt;').result_with_hash(a: 5) 有更安全的选择吗?
【解决方案4】:

如果您可以使用Erubis,您已经拥有此功能:

irb(main):001:0> require 'erubis'
#=> true
irb(main):002:0> locals = { first:'Gavin', last:'Kistner' }
#=> {:first=>"Gavin", :last=>"Kistner"}
irb(main):003:0> Erubis::Eruby.new("I am <%=first%> <%=last%>").result(locals)
#=> "I am Gavin Kistner"

【讨论】:

  • 感谢您的建议,Phrongz。但我想这次我会坚持使用 stdlib。
  • 这很方便,因为 Rails 4 现在使用 Erubis for ERB 作为其渲染引擎。
  • 对于那些使用 Rails 的人来说,这个似乎是最好的
【解决方案5】:

这里的棘手部分是不要用多余的局部变量污染绑定(就像在顶级答案中一样):

require 'erb'

class TemplateRenderer
  def self.empty_binding
    binding
  end

  def self.render(template_content, locals = {})
    b = empty_binding
    locals.each { |k, v| b.local_variable_set(k, v) }

    # puts b.local_variable_defined?(:template_content) #=> false

    ERB.new(template_content).result(b)
  end
end

# use it
TemplateRenderer.render('<%= x %> <%= y %>', x: 1, y: 2) #=> "1 2"

# use it 2: read template from file
TemplateRenderer.render(File.read('my_template.erb'), x: 1, y: 2)

【讨论】:

  • 当你说“不要污染与冗余局部变量的绑定(如在顶级答案中)”时,我很困惑,因为我对接受的最高答案进行了测试,并且没有额外的本地变量绑定中的变量,特别是 binding.local_variables.inspect 仅返回预期的变量。我同意您的解决方案效率更高,因为 OpenStruct 使用 method_missing。
  • @iheggie 几年前我测试时它被污染了。虽然现在没关系,因为您可以在 ruby​​ 2.5 中本地执行此操作,例如。 ERB.new("Hi &lt;%= name %&gt;").result_with_hash(name: "Tom")
【解决方案6】:

使用Binding的简单解决方案:

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

【讨论】:

    【解决方案7】:

    如果您想非常简单地做事,您总是可以在 ERB 模板中使用显式哈希查找。假设您使用“绑定”将一个名为“hash”的哈希变量传递给模板,它看起来像这样:

    <%= hash["key"] %>
    

    【讨论】:

    • 但是如何使用binding来传递哈希变量呢?
    • 简单。只需将哈希存储在变量中即可。然后在与变量相同的范围内,调用ERB.new(template).result(binding)Kernel#binding 将捕获范围内的所有变量,它们在 ERB 模板中可用。
    • 谢谢 - 此信息更多地属于您的主要答案,而不是评论。但是,这种方法不会遭受我上面提到的基于instance_eval 的答案所遭受的相同数据泄漏/安全问题吗?
    • 大概是您控制 ERB 模板的内容,因此安全性不是问题。如果模板不受信任,那么您遇到的问题比使用binding 引起的“数据泄漏”要大得多。由于 ERB 模板可以包含任意 Ruby 代码,因此您必须查看对呈现模板的整个过程进行沙盒处理。 (...这显然超出了这里提出的问题的范围。)
    • 是的,好点子 - 我想这不是一个安全问题,但仍然存在拼写错误等风险。意外引用了模板使用的数据集之外的内容。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-10-05
    • 1970-01-01
    • 1970-01-01
    • 2015-06-11
    相关资源
    最近更新 更多