RoR:Rails 代码中的 erb 有哪些好处?
我读过 Magnus Holm 发表的一篇令人回味的文章,标题为 Rails 3 中的块助手,他在其中指出Rails 3 已经过度扭曲了 ERB 的语法。 (在“原始”ERB 中,ERB 构造只能跨越单个语句。Rails 3 允许(甚至要求)跨越多个语句。)
这让我想知道:与 Ruby 的本机字符串处理相比,使用 ERB 的真正优势是什么?为了深入研究这一点,我采用了 ERB 文档中列出的示例,并在 ERB 和本机 Ruby 字符串中进行了尝试。事实证明,Ruby 丰富的字符串处理库使翻译变得非常简单——甚至直观。
它看起来是这样的。两者的共同点(直接从 ERB 文档中提取):
require "erb"
# Build template data class.
class Product
def initialize( code, name, desc, cost )
@code = code
@name = name
@desc = desc
@cost = cost
@features = [ ]
end
def add_feature( feature )
@features << feature
end
# Support templating of member data.
def get_binding
binding
end
end
这是用 ERB 编写的模板和扩展:
# ================================================================
# using ERB
erb_template = %{
<html>
<head><title>Ruby Toys -- <%= @name %></title></head>
<body>
<h1><%= @name %> (<%= @code %>)</h1>
<p><%= @desc %></p>
<ul>
<% @features.each do |f| %>
<li><b><%= f %></b></li>
<% end %>
</ul>
<p>
<% if @cost < 10 %>
<b>Only <%= @cost %>!!!</b>
<% else %>
Call for a price, today!
<% end %>
</p>
</body>
</html>
}.gsub(/^ /, '')
rhtml = ERB.new(erb_template)
# Produce results
@r1 = rhtml.result(toy.get_binding)
这是用纯 Ruby 编写的模板:
# ================================================================
# using native Ruby strings
ruby_template = %q{"
<html>
<head><title>Ruby Toys -- #{ @name }</title></head>
<body>
<h1>#{ @name } (#{ @code })</h1>
<p>#{ @desc }</p>
<ul>
#{ @features.map do |f|
"<li><b>#{f}</b></li>\n"
end.join }
</ul>
<p>
#{ if @cost < 10
"<b>Only #{ @cost }!!!</b>"
else
"Call for a price, today!"
end
}
</p>
</body>
</html>
"}
# Produce results
@r2 = eval(ruby_template, toy.get_binding)
它们产生相同的结果(模空格)。 ERB 是更简单还是更困难实际上取决于品味和经验。从ERB的问题数量来看,<%= ... %>与 <% ... %>与 <%= ... -%> 相比,似乎许多人可能更容易坚持直接使用 Ruby。
冒着发起某种圣战的风险,当原生 Ruby 可以完成同样的工作时,为什么还要费心使用 ERB呢?你觉得ERB有用吗? Rails 也应该接受“原生 Ruby”模板吗?
I read an evocative post by Magnus Holm entitled Block Helpers in Rails 3, in which he points out that Rails 3 has bent the syntax of ERB too far. (In 'original' ERB, an ERB construct can only span a single statement. Rails 3 allows -- even requires -- spanning multiple statements.)
Which leads me to wonder: what are the real advantages to using ERB over Ruby's native string processing? To dig into this, I took the example listed in the ERB documentation and tried it both in ERB and native Ruby strings. It turns out that Ruby's rich library of string processing makes the translation really simple -- even intuitive.
Here's how it looks. Common to both (lifted directly from the ERB documentation):
require "erb"
# Build template data class.
class Product
def initialize( code, name, desc, cost )
@code = code
@name = name
@desc = desc
@cost = cost
@features = [ ]
end
def add_feature( feature )
@features << feature
end
# Support templating of member data.
def get_binding
binding
end
end
Here's the template and the expansion written in ERB:
# ================================================================
# using ERB
erb_template = %{
<html>
<head><title>Ruby Toys -- <%= @name %></title></head>
<body>
<h1><%= @name %> (<%= @code %>)</h1>
<p><%= @desc %></p>
<ul>
<% @features.each do |f| %>
<li><b><%= f %></b></li>
<% end %>
</ul>
<p>
<% if @cost < 10 %>
<b>Only <%= @cost %>!!!</b>
<% else %>
Call for a price, today!
<% end %>
</p>
</body>
</html>
}.gsub(/^ /, '')
rhtml = ERB.new(erb_template)
# Produce results
@r1 = rhtml.result(toy.get_binding)
And here's the template written in pure Ruby:
# ================================================================
# using native Ruby strings
ruby_template = %q{"
<html>
<head><title>Ruby Toys -- #{ @name }</title></head>
<body>
<h1>#{ @name } (#{ @code })</h1>
<p>#{ @desc }</p>
<ul>
#{ @features.map do |f|
"<li><b>#{f}</b></li>\n"
end.join }
</ul>
<p>
#{ if @cost < 10
"<b>Only #{ @cost }!!!</b>"
else
"Call for a price, today!"
end
}
</p>
</body>
</html>
"}
# Produce results
@r2 = eval(ruby_template, toy.get_binding)
These produce the same results (modulo whitespace). Whether the ERB is simpler or more difficult is really a matter of taste and experience. Judging from the number of questions about ERB and <%= ... %> vs <% ... %> vs <%= ... -%>, it appears that many people might have an easier time sticking to straight Ruby.
At the risk of starting some sort of holy war, why bother with a ERB when the native Ruby does the same job? Do you think ERB is useful? Should Rails accept "native Ruby" templates as well?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
马格努斯·霍尔姆在这里;很高兴看到您喜欢这篇文章:-)
首先让我们看一下模板(是的,插值是一个模板),它是使用常规代码构造字符串的演变:
如果我们看一下这段代码,很明显存在三个重复的模式:
str << “静态”
str <<表达式
@users.each
ERB 的伟大之处在于它是这些模式的完美同构,这意味着用户可以按照常规方式构建字符串来思考它。这只是一种构造字符串的方法,更关注静态部分(因为它们很常见)而不是动态部分。
作为“纯粹主义者”,您已经注意到这不是最低级别。这是完全正确的:只要您可以评估图灵完备的代码,您的模板引擎就变得图灵完备,并且从技术上讲您不需要任何其他东西。当您可以简单地嵌套模板引擎时,您不需要块。
现在我们需要考虑低级和易于理解之间的区别。是的,当你移除建筑部件时,你最终会得到一个更小的核心,但这并不一定会让它更容易理解。您可以与 GOTO 进行类比:您可以使用一个概念(GOTO)创建任何循环/条件,但事实证明,使用 if/while 语句更容易推理代码。为什么?因为图案出现了!与每次查看这些模式时都从概念上解析这些模式不同,创建我们可以立即理解的抽象会更容易。
在您的示例中,我相信有一种模式可以在任何模板中使用:
@users.map { |f|代码}.join
。这正是 ERB 试图抽象出来的内容,这样您就可以忘记逻辑的细节和推理。我还相信,如果您使模板引擎更简单,但仍然使其图灵完备,那么您无论如何都会抽象出这些细节。您将意识到出现了一种模式,并且作为 DRY 编码员,您将开始创建帮助程序等。您实际上是在另一个模板引擎之上实现您自己的小模板引擎。现在,一切都取决于基础语言的语法灵活性,无论您是否能够在没有太多其他噪音的情况下抽象出真正的细节。例如,有一些 Lisp 风格支持静态类型和模式匹配,但通常它无法击败专门针对该问题设计的语法。
那么,与 Ruby 原生字符串处理相比,使用 ERB 的真正优势是什么?一句话:它为您提供了常见模式的良好语法。
是和不是。我认为插值绝对不是正确的方法。您将在模板中看到模式,并且在模板方面没有什么比自定义语法更好的了。我认为 ERB 非常有用,但它在某些方面有所欠缺:它没有块也是表达式的概念:
我不知道是否可以用语法修复这个问题,但现在它在每个框架中都是“固定”的,并且无法编写跨多个框架工作的块助手。我希望看到一种更“官方”的方式来处理这个问题。
Tilt 已经做到了,最好的解决方案是 Rails 切换到 Tilt,但我不认为“原生 Ruby”毕竟很有用。如果它是一个简短的模板,您当然可以在代码中使用插入的字符串。如果它是一个大模板并且您要将其移出 Ruby 文件,为什么不使用为模板设计的语法呢?
Magnus Holm here; good to see that you enjoyed the article :-)
First let's have a look at templates (yes, interpolation is a template) as an evolution from constructing a string with regular code:
If we look at this code, it's evident that there are three patterns that repeat themselves:
str << "Static"
str << expresstion
@users.each
The great thing about ERB is that it's a perfect isomorphism of these patterns, which means that the user can think of it in terms of constructing the string the regular way. It's simply a way to construct a string with more focus on the static parts (since they are common) than the dynamic parts.
As the "purist" you are, you've noticed that this is not the lowest level. That is completely true: As long as you can evaluate Turing complete code, your template engine becomes Turing complete and technically you don't need anything else. You don't need blocks when you can simply nest your template engine.
Now we need think about the difference between low level and easy to understand. Yes, as you remove building parts you end up with a smaller core, but that doesn't necessarily make it easier to understand. You could draw an analogy to GOTOs: You can create any looping/conditionals using one single concept (GOTOs), but it turns out that it's easier to reason about code with if/while-statements. Why? Because a pattern appears! Instead of conceptually parse these patterns every time we look at them, it's easier to create abstractions which we can understand instantly.
In your example, there's one pattern which I believe will be used in any template:
@users.map { |f| code }.join
. This is exactly what ERB tries to abstract away so you can forget about the details and reason about your logic instead.I also believe that if you're making a template engine simpler, but still make it Turing complete, you will abstract away these details anyway. You will realize that a pattern emerges and, as the DRY coder you are, you will start creating helpers and such. You're actually implementing your own little template engine on top of another. Now everything depends on the syntax flexibility of the base language whether or not you manage to abstract away the real details without too much other noise. For instance, there are Lisp flavours which supports static typing and pattern matching, but often it can't beat a syntax designed especially for the problem.
So, what are the real advantages to using ERB over Ruby's native string processing? In one sentence: It gives you nice syntax for common patterns.
Yes and no. I think interpolation is definitely not the right way. You will see patterns in your templates and nothing beats a custom syntax when it comes to templates. I think ERB is quite useful, but it lacks in some ways: It doesn't have the concept that blocks are also expressions:
I have no idea if you can fix this with syntax, but right now it's "fixed" in each framework and there's no way to write block helpers that works across several frameworks. I'd love to see a more "official" way to deal with this.
Tilt already does, and the best solution would be if Rails switched over to Tilt, but I don't think "native Ruby" is that useful after all. If it's a short template, you can of course just use an interpolated string right in your code. And if it's a big template and you're going to move it out of your Ruby file, why not use a syntax designed for templates?
Rails 是一个固执己见的软件。它附带了一堆“默认”的做事方式,我猜核心团队同意这是他们的首选做事方式 - erb、Test::Unit、原型等,但没有什么可以阻止你改变这些(更重要的是)与轨道 3)。
在我看来,Rails 中的 erb,尽管不太像普通的 erb,但比 ruby 字符串插值要好得多——尤其是在块方面。在我看来,必须将块内的 HTML 分解回字符串看起来很可怕。
但话又说回来,haml 对我来说看起来很可怕,而其他人则对它发誓并讨厌 erb。这一切都取决于您的喜好。这就是为什么 Rails 使开发人员可以轻松创建自己的模板系统。我不知道是否存在,但是没有什么可以阻止开发人员创建一个仅使用这样的 ruby 字符串的模板系统。
因此,在回答“Rails 也应该接受“原生 Ruby”模板吗?”时,它已经接受了。只要其他人实现它即可;)
Rails is opinionated software. It ships with a bunch of 'default' ways of doing things which I guess the core team agree is their preferred way of doing things - erb, Test::Unit, prototype, etc. but there's nothing to stop you changing these (more so with Rails 3).
In my opinion, erb in Rails, even though not quite like normal erb is far, far nicer than ruby string interpolation - especially when it comes to blocks. Having to break back into strings for the HTML within the block looks horrible to me.
But then again, haml looks horrible to me while others swear by it and hate erb. It's all down to your preference. That's why Rails makes it very easy for developers to create their own templating systems. I don't know if one exists, but there's nothing stopping devs from creating a templating system which uses solely ruby strings like that.
So, in answer to "Should Rails accept "native Ruby" templates as well?", it already does. So long as someone else implements it ;)