Ruby on Rails 中的 Action View 完全指南:模板、局部模板与布局
Action View 是 Ruby on Rails 中 MVC 架构的"V",负责把控制器准备好的数据渲染成最终的 HTML 响应。本指南以官方文档为基础,结合本仓库 actionview 组件的真实源码,系统讲解 Action View 的工作方式、ERB/Jbuilder/Builder 三种模板、partials 局部模板的全部渲染选项、布局机制以及本地化视图,帮助你真正读懂 Rails 视图层并能写出高质量的可维护视图代码。
本仓库以 monorepo 形式完整收录了 Ruby on Rails 的全部组件源码,其中 Action View 的实现位于 actionview/ 目录,配套的官方文档即本指南的 guides/source/action_view_overview.md,所有示例与结论均可回到对应源码中逐一验证。
什么是 Action View?
Action View 是 MVC(Model-View-Controller)中的 V。Action Controller 与 Action View 配合处理 Web 请求:Action Controller 负责与模型层(MVC 中的 Model)通信并取回数据,Action View 则负责使用这些数据为 Web 请求渲染出响应正文(response body)。
默认情况下,Action View 的模板(也常常直接被称为 "views")使用 Embedded Ruby(ERB) 编写,即在 HTML 文档中嵌入 Ruby 代码。同时,Action View 还提供大量 helper 方法用于动态生成表单、日期、字符串相关的 HTML 标签,你也可以按需在应用中添加自定义 helper。
注意:Action View 可以借助 Active Model 的特性(如
to_param、to_partial_path)来简化代码,但这不意味着 Action View 依赖 Active Model。Action View 是一个完全独立的包,可以和任意 Ruby 类库配合使用——从仓库结构可以印证这一点:actionview/actionview.gemspec 定义了独立发布的 gem,而 actionview/lib/action_view.rb 是它的独立入口文件。
在 Rails 中如何使用 Action View
Action View 模板存放在 app/views 目录下的各个子目录中,每个子目录的名字与对应的控制器同名,子目录内的视图文件用来渲染对应控制器动作(action)的响应。
例如使用脚手架生成 article 资源时,Rails 会在 app/views/articles 下生成如下文件:
$ bin/rails generate scaffold article
[...]
invoke scaffold_controller
create app/controllers/articles_controller.rb
invoke erb
create app/views/articles
create app/views/articles/index.html.erb
create app/views/articles/edit.html.erb
create app/views/articles/show.html.erb
create app/views/articles/new.html.erb
create app/views/articles/_form.html.erb
[...]
文件名遵循 Rails 命名约定:视图文件与对应的控制器动作同名,例如 index.html.erb 对应 index 动作、edit.html.erb 对应 edit 动作。
只要遵循这一命名约定,控制器动作结束时 Rails 就会自动查找并渲染匹配的视图,无需在动作中显式声明。例如 articles_controller.rb 中的 index 动作会自动渲染 app/views/articles/ 目录下的 index.html.erb。文件的名字与位置都很重要。
最终返回给客户端的 HTML 由三部分组合而成:
.html.erbERB 文件本身;- 包裹它的 layout(布局)模板;
- ERB 文件可能引用的所有 partials(局部模板)。
下面分别详细介绍 Templates、Partials、Layouts 三部分。
Templates(模板)
Action View 模板可以用多种格式编写,Rails 依据文件扩展名区分不同的模板系统:
| 扩展名 | 渲染系统 | 产出内容 |
|---|---|---|
.erb |
Embedded Ruby | HTML 响应 |
.jbuilder |
Jbuilder gem | JSON 响应 |
.builder |
Builder::XmlMarkup 库 | XML 响应 |
例如使用 ERB 模板系统的 HTML 文件扩展名为 .html.erb,使用 Jbuilder 的 JSON 文件扩展名为 .json.jbuilder。其他类库也可以注册自己的模板类型与扩展名。
ERB
ERB 模板就是通过 <% %> 与 <%= %> 这类特殊标签,在静态 HTML 中掺入 Ruby 代码。
当 Rails 处理以 .html.erb 结尾的视图时,会执行其中嵌入的 Ruby 代码,用动态输出替换 ERB 标签,再与静态 HTML 拼合,形成最终的 HTML 响应。两个标签的区别在于:
<% %>(不带等号):只执行 Ruby 代码,不输出结果,典型场景是条件判断与循环;<%= %>:执行并输出,典型场景是输出模型属性,如下面的person.name:
<h1>Names</h1>
<% @people.each do |person| %>
Name: <%= person.name %><br>
<% end %>
循环体用普通嵌入标签 <% %> 搭建,循环内输出姓名时用输出标签 <%= %>。
需要注意,print、puts 这类函数不会被渲染到 ERB 视图输出中,所以下面的写法是错误的:
<%# WRONG %>
Hi, Mr. <% puts "Frodo" %>
上面的例子同时展示了 ERB 注释的写法:用 <%# %> 标签。
如需抑制行首行尾的空白,可以使用 <%- 与 -%>,它们分别与 <%、%> 通用(trim 模式默认开启,见下文源码分析)。
源码层面的印证:ERB 处理器的实现位于 actionview/lib/action_view/template/handlers/erb.rb。从源码结构可以看到它的几个默认行为(erb.rb#L13-L24):
erb_trim_mode默认值为"-",这正是<%-、-%>生效的开关;erb_implementation默认指向Erubi(一个高性能的 ERB 引擎);escape_ignore_list默认豁免"text/plain"等 MIME 类型不做转义。
其内部会调用 erubi.rb 把模板源码编译为写入 @output_buffer 的 Ruby 代码:普通文本通过 safe_append 追加,<%= %> 表达式则通过 append(普通)或 safe_expr_append(转义)写入输出缓冲区。这就是 ERB 之所以能安全拼接 HTML 输出的底层原理。
Jbuilder
Jbuilder 是由 Rails 团队维护、并已包含在 Rails 默认 Gemfile 中的 gem,用于通过模板构建 JSON 响应。如果项目中没有,可在 Gemfile 中添加:
gem "jbuilder"
带有 .jbuilder 扩展名的模板会自动获得一个名为 json 的 Jbuilder 对象。基础示例:
json.name("Alex")
json.email("alex@example.com")
会输出:
{
"name": "Alex",
"email": "alex@example.com"
}
Builder
Builder 模板是比 ERB 更"编程式"的另一种方案,与 Jbuilder 类似,区别在于它生成的是 XML 而不是 JSON。带有 .builder 扩展名的模板会自动获得一个名为 xml 的 XmlMarkup 对象。基础示例:
xml.em("emphasized")
xml.em { xml.b("emph & bold") }
xml.a("A Link", "href" => "https://rubyonrails.org")
xml.target("name" => "compile", "option" => "fast")
会输出:
<em>emphasized</em>
<em><b>emph & bold</b></em>
<a href="https://rubyonrails.org">A link</a>
<target option="fast" name="compile" />
任何带代码块的方法都会被当作一个可嵌套标记的 XML 标签。例如:
xml.div {
xml.h1(@person.name)
xml.p(@person.bio)
}
输出类似:
<div>
<h1>David Heinemeier Hansson</h1>
<p>A product of Danish Design during the Winter of '79...</p>
</div>
模板编译(Template Compilation)
默认情况下,Rails 会把每个模板编译成一个渲染方法(method)。在 development 环境中,当你修改模板后,Rails 会检查文件的修改时间并重新编译它。
对于需要"不同页面片段各自独立缓存与过期"的场景,可借助 Fragment Caching,详见 缓存指南 中的 fragment caching 章节。
补充:模板编译的产物就是上文 Erubi 生成的那段 Ruby 源码方法;这也解释了后文"Strict Locals"之所以重要——partials 编译成的正是这种签名各异的 Ruby 方法。
Partials(局部模板)
Partial templates(通常简称 "partials")用来把视图模板拆分成更小的、可复用的片段。通过 partials,你可以把主模板中的一段代码抽取到独立的小文件中,再在主模板中渲染它,同时还能从主模板向 partial 传入数据。
渲染 Partial
在视图中使用 render 方法渲染 partial:
<%= render "product" %>
这会在当前目录查找名为 _product.html.erb 的文件进行渲染。按约定,partial 文件名以下划线开头,用来与普通视图区分。但在视图中引用 partial 时不加下划线,即便引用其他目录下的 partial 也一样:
<%= render "application/product" %>
上面这行会查找并显示 app/views/application/ 下的 _product.html.erb 文件。
源码印证:partial 渲染的分发逻辑位于 actionview/lib/action_view/renderer/partial_renderer.rb。其中 find_template(partial_renderer.rb#L278-L281)有一个关键细节:当路径字符串包含 / 时,prefixes 为空列表,即模板按完整路径解析、不再叠加当前控制器的视图前缀;反之则会在当前控制器的前缀下查找。
用 Partials 简化视图
可以把 partials 理解为"视图里的方法":把细节从视图中移走,让你一眼看穿页面主干。例如:
<%= render "application/ad_banner" %>
<h1>Products</h1>
<p>Here are a few of our fine products:</p>
<% @products.each do |product| %>
<%= render partial: "product", locals: { product: product } %>
<% end %>
<%= render "application/footer" %>
其中 _ad_banner.html.erb、_footer.html.erb 承载多个页面共享的内容;当你专注开发 Products 页面时,不必关心这些区块的细节。上面的例子还用到了 _product.html.erb,它专门负责渲染单个产品对象,在 @products 集合上逐个渲染。
使用 locals: 选项向 Partial 传参
渲染 partial 时可以用 locals: 选项哈希传数据。locals: 中的每个键都会成为该 partial 内的局部变量:
<%# app/views/products/show.html.erb %>
<%= render partial: "product", locals: { my_product: @product } %>
<%# app/views/products/_product.html.erb %>
<%= tag.div id: dom_id(my_product) do %>
<h1><%= my_product.name %></h1>
<% end %>
"partial-local variable" 指只在该 partial 内可见的局部变量。上例中 my_product 就是这样一个变量,它由调用方把 @product 赋值传入。(通常我们直接把它命名为 product,这里特意用 my_product 以便和实例变量、模板名区分。)
由于 locals 是哈希,需要时可以传多个变量,例如 locals: { my_product: @product, my_reviews: @reviews }。
硬性约束:如果模板引用了没有通过 locals: 传入的变量,会抛出 ActionView::Template::Error:
<%# app/views/products/_product.html.erb %>
<%= tag.div id: dom_id(my_product) do %>
<h1><%= my_product.name %></h1>
<%# => raises ActionView::Template::Error for `product_reviews` %>
<% product_reviews.each do |review| %>
<%# ... %>
<% end %>
<% end %>
使用 local_assigns
每个 partial 都有 local_assigns 方法可用,它能读取通过 locals: 传入的键。如果某次渲染未传入 :some_key,partial 内 local_assigns[:some_key] 的值就是 nil。例如下面只传了 product,所以 product_reviews 为 nil:
<%# app/views/products/show.html.erb %>
<%= render partial: "product", locals: { product: @product } %>
<%# app/views/products/_product.html.erb %>
<% local_assigns[:product] # => "#<Product:0x0000000109ec5d10>" %>
<% local_assigns[:product_reviews] # => nil %>
local_assigns 的典型用途是:可选地传入某个局部变量,再依据它是否被设置来决定 partial 内的行为:
<% if local_assigns[:redirect] %>
<%= form.hidden_field :redirect, value: true %>
<% end %>
Active Storage 的 _blob.html.erb 也是范例:渲染该 partial 时依据 in_gallery 局部变量是否设置来决定图片尺寸:
<%= image_tag blob.representation(resize_to_limit: local_assigns[:in_gallery] ? [ 800, 600 ] : [ 1024, 768 ]) %>
不带 partial 与 locals 键的 render 简写
如果只需 partial 和 locals 两个选项,可以省略键名、只写值:
<%= render "product", product: @product %>
这等价于:
<%= render partial: "product", locals: { product: @product } %>
还可以基于约定进一步简写——直接渲染对象:
<%= render @product %>
它会查找 app/views/products/ 下的 _product.html.erb,并把局部变量 product 设为 @product。背后的机制是:每个 Active Model 对象都实现了 to_partial_path,partial 渲染器据此推导出 partial 的路径与默认局部变量名。
as 与 object 选项
默认情况下,传入模板的对象会存放在与模板同名的局部变量中。所以:
<%= render @product %>
在 _product.html.erb 内部,@product 会以局部变量 product 出现,等价于:
<%= render partial: "product", locals: { product: @product } %>
object 选项用于指定另一个来源对象。当模板所需的对象不在同名实例变量里(例如在其他实例变量或局部变量中)时很有用。例如,把:
<%= render partial: "product", locals: { product: @item } %>
改写成:
<%= render partial: "product", object: @item %>
这会实例变量 @item 赋给 partial 局部变量 product。若想连局部变量的名字也改掉,就用 :as 选项:
<%= render partial: "product", object: @item, as: "item" %>
等价于:
<%= render partial: "product", locals: { item: @item } %>
渲染集合(Rendering Collections)
视图经常需要遍历一个集合(如 @products)并为每个元素渲染同一 partial。Rails 把这种模式封装成一次调用即渲染整个数组的方法:
<% @products.each do |product| %>
<%= render partial: "product", locals: { product: product } %>
<% end %>
可以改写成一行:
<%= render partial: "product", collection: @products %>
用集合渲染时,每个 partial 实例都能通过"以 partial 命名的变量"访问当前集合成员——既然 partial 是 _product.html.erb,就用 product 指代当前被渲染的集合成员。
也有基于约定的简写:
<%= render @products %>
它假定 @products 是 Product 实例的集合。Rails 按约定从集合中模型的名字(这里是 Product)推导出使用的 partial。更有趣的是,这种简写甚至可以渲染由不同模型实例混合而成的集合——Rails 会为每个成员自动挑选正确的 partial。
源码印证:集合渲染实现在 actionview/lib/action_view/renderer/collection_renderer.rb。其中:
- 同质集合走
SameCollectionIterator(collection_renderer.rb#L60-L76); - 混合模型集合走
MixedCollectionIterator,每个成员携带自己的 partial 路径(collection_renderer.rb#L100-L110); - 若集合本身是 Active Record 关联,会使用
PreloadCollectionIterator自动预加载(collection_renderer.rb#L78-L98),这是 N+1 查询防护的一环。
另外,当 render 的 collection 为 nil 或空时,render 返回 nil——因此你可以这样显示空集合的替代文案:
<%= render(partial: "ad", collection: @advertisements) || "There's no ad to be displayed" %>
分隔模板(Spacer Templates)
通过 :spacer_template 选项可以指定一个"插在每两段主 partial 之间"的第二个 partial:
<%= render partial: @products, spacer_template: "product_ruler" %>
Rails 会在每两段 _product.html.erb 之间渲染一次 _product_ruler.html.erb(不向其传任何数据)。源码中该逻辑见 collection_renderer.rb#L161-L166。
计数变量(Counter Variables)
集合渲染时,partial 内还会得到一个计数变量,命名为"partial 名 + _counter"。例如渲染集合 @products 时,_product.html.erb 可以访问 product_counter,它从 0 开始记录 partial 已被渲染的次数:
<%# index.html.erb %>
<%= render partial: "product", collection: @products %>
<%# _product.html.erb %>
<%= product_counter %> # 0 for the first product, 1 for the second product...
用 as: 改了局部变量名时同样生效——例如 as: :item 时计数变量为 item_counter。
注意:渲染混合模型的集合时,无论渲染的模型类是什么,计数变量都会逐个递增。
除了 _counter,源码还同时暴露了 _iteration 对象:集合渲染器会构造一个 PartialIteration 实例(见 collection_renderer.rb#L6-L31),它携带 index、size,并提供 first? 与 last? 便捷判断方法,_counter 正是 iteration 的 index 的向后兼容别名。
下面两节(Strict Locals 与 Local Assigns with Pattern Matching)属于 partials 的高级用法,为了完整性一并收录。
local_assigns 与模式匹配
既然 local_assigns 是一个 Hash,它就兼容 Ruby 3.1 引入的模式匹配赋值运算符:
local_assigns => { product:, **options }
product # => "#<Product:0x0000000109ec5d10>"
options # => {}
当除 :product 之外还传入了其他键时,可以把它们"打包"成一个局部 Hash,再整体展开(splat)进 helper 调用:
<%# app/views/products/_product.html.erb %>
<% local_assigns => { product:, **options } %>
<%= tag.div id: dom_id(product), **options do %>
<h1><%= product.name %></h1>
<% end %>
<%# app/views/products/show.html.erb %>
<%= render "products/product", product: @product, class: "card" %>
<%# => <div id="product_1" class="card">
# <h1>A widget</h1>
# </div>
%>
模式匹配赋值还支持变量重命名:
local_assigns => { product: record }
product # => "#<Product:0x0000000109ec5d10>"
record # => "#<Product:0x0000000109ec5d10>"
product == record # => true
也可以用 fetch 有条件的读取某个键,并在键不在 locals: 中时回退到默认值:
<%# app/views/products/_product.html.erb %>
<% local_assigns.fetch(:related_products, []).each do |related_product| %>
<%# ... %>
<% end %>
把 Ruby 3.1 模式匹配赋值与 Hash#with_defaults 结合,还能实现紧凑的"默认局部变量赋值":
<%# app/views/products/_product.html.erb %>
<% local_assigns.with_defaults(related_products: []) => { product:, related_products: } %>
<%= tag.div id: dom_id(product) do %>
<h1><%= product.name %></h1>
<% related_products.each do |related_product| %>
<%# ... %>
<% end %>
<% end %>
Strict Locals(严格局部变量签名)
Action View 的 partials 在底层会被编译成普通的 Ruby 方法。由于 Ruby 无法动态创建局部变量,传入 partial 的 locals 的每一种组合都必须编译出一个新版本:
<%# app/views/articles/show.html.erb %>
<%= render partial: "article", layout: "box", locals: { article: @article } %>
<%= render partial: "article", layout: "box", locals: { article: @article, theme: "dark" } %>
上面的代码片段会导致 partial 被编译两次,更耗时间也占用更多内存:
def _render_template_2323231_article_show(buffer, local_assigns, article:)
# ...
end
def _render_template_3243454_article_show(buffer, local_assigns, article:, theme:)
# ...
end
组合数少时问题不大;一旦组合很多,就会浪费可观的内存并拖慢编译速度。为此可以用 strict locals 固定 partial 的编译签名,确保只编译一个版本:
<%# locals: (article:, theme: "light") -%>
...
locals: 签名使用与 Ruby 方法签名相同的语法,可以强制规定模板接受哪些(以及多少个)局部变量、设置默认值等。
几个 locals: 签名的例子:
<%# app/views/messages/_message.html.erb %>
<%# locals: (message:) -%>
<%= message %>
上面把 message 声明为必填局部变量。如果渲染时没有传 :message,就会抛出异常:
render "messages/message"
# => ActionView::Template::Error: missing local: :message for app/views/messages/_message.html.erb
设置了默认值时,message 未在 locals: 中传入也能使用默认值:
<%# app/views/messages/_message.html.erb %>
<%# locals: (message: "Hello, world!") -%>
<%= message %>
渲染时省略 :message 会采用签名中的默认值:
render "messages/message"
# => "Hello, world!"
传入签名中未声明的局部变量同样会抛异常:
render "messages/message", unknown_local: "will raise"
# => ActionView::Template::Error: unknown local: :unknown_local for app/views/messages/_message.html.erb
用双 splat 运算符 ** 可以允许"额外的可选局部变量参数":
<%# app/views/messages/_message.html.erb %>
<%# locals: (message: "Hello, world!", **attributes) -%>
<%= tag.p(message, **attributes) %>
也可以把 locals: 设为空的 () 来彻底禁用局部变量:
<%# app/views/messages/_message.html.erb %>
<%# locals: () %>
此时传入任何局部变量都会抛异常:
render "messages/message", unknown_local: "will raise"
# => ActionView::Template::Error: no locals accepted for app/views/messages/_message.html.erb
Action View 会在任何支持 # 前缀注释的模板引擎中解析 locals: 签名,并且会读取 partial 中任意一行的签名。
CAUTION:只支持关键字参数。定义位置参数或块参数会在渲染时抛出 Action View 错误。
此外,local_assigns 方法不包含 locals: 签名中声明的默认值。若要访问与 Ruby 保留字(如 class、if)同名的带默认值局部变量,可通过 binding.local_variable_get 读取:
<%# locals: (class: "message") %>
<div class="<%= binding.local_variable_get(:class) %>">...</div>
Layouts(布局)
Layout 可以看作"包裹在控制器动作渲染结果外面的公共视图模板",一个 Rails 应用可以拥有多个布局。例如:为已登录用户准备一套布局(包含贯穿许多控制器动作的顶层导航),为站点的营销部分准备另一套;不同布局的 header 与 footer 可以各不相同。
查找当前控制器动作的布局时,Rails 首先在 app/views/layouts 中查找与控制器同名的文件。例如渲染 ProductsController 的动作时会使用 app/views/layouts/products.html.erb。如果控制器专属布局不存在,则回退使用 app/views/layouts/application.html.erb。
一个简单的 application.html.erb 布局示例:
<!DOCTYPE html>
<html>
<head>
<title><%= "Your Rails App" %></title>
<%= csrf_meta_tags %>
<%= csp_meta_tag %>
<%= stylesheet_link_tag "application", "data-turbo-track": "reload" %>
<%= javascript_importmap_tags %>
</head>
<body>
<nav>
<ul>
<li><%= link_to "Home", root_path %></li>
<li><%= link_to "Products", products_path %></li>
<!-- Additional navigation links here -->
</ul>
</nav>
<%= yield %>
<footer>
<p>© <%= Date.current.year %> Your Company</p>
</footer>
在这个布局中,视图内容会渲染在 <%= yield %> 的位置,并被相同的 <head>、<nav>、<footer> 内容包围。该示例也演示了布局里常见的一批 helper:csrf_meta_tags、csp_meta_tag、stylesheet_link_tag、javascript_importmap_tags、link_to 等。
Rails 还提供了更多为特定控制器与动作指定布局的方式,整体布局机制的细节见 Rails 中的布局与渲染指南。布局相关的查找与渲染实现主要位于 actionview/lib/action_view/layouts.rb。
Partial Layouts(局部模板的布局)
Partial 也可以套用自己的布局——它不同于套在控制器动作外面的布局,但工作机制类似。
假设某页要展示一篇文章,且需要用 div 包裹以便显示。首先创建一个 Article:
Article.create(body: "Partial Layouts are cool!")
在 show 模板中,让 _article partial 套上 box 布局渲染:
<%# app/views/articles/show.html.erb %>
<%= render partial: 'article', layout: 'box', locals: { article: @article } %>
box 布局只是把 _article partial 包进一个 div:
<%# app/views/articles/_box.html.erb %>
<div class="box">
<%= yield %>
</div>
注意:partial 布局能够访问传给 render 调用的局部变量 article(虽然这个例子中 _box.html.erb 没有用到它)。
与全局布局不同,partial 布局的文件名仍然带有下划线前缀。
除了 yield,也可以在 partial 布局中直接渲染一段代码块。例如没有 _article partial 时,可以这样:
<%# app/views/articles/show.html.erb %>
<%= render(layout: 'box', locals: { article: @article }) do %>
<div>
<p><%= article.body %></p>
</div>
<% end %>
若沿用上面同一个 _box partial,这段代码与前一个示例产生相同输出。
源码印证:partial 布局在 partial_renderer.rb#L246-L254 中被处理——先渲染 partial 主体,再渲染 layout 并把 partial 内容作为块传入;渲染全程通过 ActiveSupport::Notifications 发送 render_partial.action_view 事件(partial_renderer.rb#L261-L276),这部分事件由 actionview/lib/action_view/log_subscriber.rb 订阅并输出到日志(日志中常见的 "Rendered ... within ..." 即来源于此)。
集合与 Partial Layouts
渲染集合时同样可以使用 :layout 选项:
<%= render partial: "article", collection: @articles, layout: "special_layout" %>
布局会为集合中的每一个元素与对应 partial 一起渲染。当前对象与对象计数变量(上例中的 article 与 article_counter)在布局内与在 partial 内一样可用。该逻辑同样实现在 collection_renderer.rb 的逐元素渲染中(locals[as] = object、locals[counter] = index、locals[iteration] = partial_iteration,见 collection_renderer.rb#L186-L201)。
Helpers(辅助方法)
Rails 为 Action View 提供了大量 helper 方法,涵盖的能力包括:
- 格式化日期、字符串与数字;
- 生成指向图片、视频、样式表等资源的 HTML 链接;
- 清洗(sanitize)内容;
- 生成表单;
- 本地化内容。
这些 helper 的源码在仓库中按职责分布在 actionview/lib/action_view/helpers/ 下,例如 url_helper.rb(链接)、asset_tag_helper.rb(样式表/图片标签)、tag_helper.rb(tag.div 等通用标签)、form_helper.rb 与 form_options_helper.rb(表单)、number_helper.rb(数字格式化)、date_helper.rb(日期选择)、translation_helper.rb(本地化翻译)等。
各 helper 的详细用法参见 Action View Helpers 指南 与 Action View Form Helpers 指南。
本地化视图(Localized Views)
Action View 支持依据当前 locale 渲染不同的模板。
例如假设有一个带 show 动作的 ArticlesController:默认情况下调用该动作渲染 app/views/articles/show.html.erb。如果设置 I18n.locale = :de,Action View 会优先尝试渲染 app/views/articles/show.de.html.erb;若本地化模板不存在,就退回未加语言标记的版本。这意味着你不必为所有情况都提供本地化视图,但只要有,它们就会被优先使用。
源码印证:这一查找行为与视图查找器把 locale 注册为"模板细节"有关。在 actionview/lib/action_view/lookup_context.rb 中可以看到:
register_detail(:locale) do
locales = [I18n.locale]
locales.concat(I18n.fallbacks[I18n.locale]) if I18n.respond_to? :fallbacks
locales << I18n.default_locale
locales.uniq!
locales
end
即查找模板时 locale 的候选序列为:当前 I18n.locale → 若有 fallback 机制则追加 I18n.fallbacks 提供的回退 locale → 最后追加 I18n.default_locale 并去重。这就解释了为什么 :de 下会优先命中 show.de.html.erb、找不到再退回无标记版本,也让 Rails 应用能利用 i18n-fallbacks 在德语缺失时自动回退到其它语言视图。
同样的技巧也可以用于本地化 public 目录下的 rescue 页面。例如设置 I18n.locale = :de 并创建 public/500.de.html 与 public/404.de.html,即可获得本地化的错误处理页面。
更多细节参见 国际化(I18n)指南。
小结与延伸阅读
总结一下本指南的核心结论:
- 职责边界:Action View 是 MVC 的 V,只负责把数据渲染为响应正文,与 Action Controller 解耦、也不依赖 Active Model,可在任何 Ruby 项目中独立使用。
- 模板三分:
.erb(HTML)、.jbuilder(JSON)、.builder(XML)三种模板系统由文件扩展名驱动;ERB 模板经由 Erubi 编译为写入输出缓冲区的 Ruby 方法。 - Partial 是视图复用的主力:从基础的
render "product",到locals:/object:/as:/collection:/spacer_template:等全套选项,再到local_assigns、Ruby 3.1 模式匹配与 strict locals 签名,Action View 为局部模板提供了从入门到高性能的全部手段。 - Layout 保证页面骨架一致:按控制器名查找布局、回退
application.html.erb,partial 也可以套用自己的布局(可配合集合渲染)。 - 本地化视图:依赖
I18n.locale与模板查找器的 locale 候选序列,实现show.de.html.erb这类按语言自动选视图的能力。
若要进一步深入,本仓库还提供了完整的 Action View 测试套件(见 actionview/test/,例如 actionview/test/activerecord/render_partial_with_record_identification_test.rb 覆盖了基于对象身份渲染 partial 的行为,actionview/test/template/compiled_templates_test.rb 则验证模板编译相关机制),可作为阅读实现与学习行为边界的补充资料。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0627
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00