首页
/ Ruby on Rails 中的 Action View 完全指南:模板、局部模板与布局

Ruby on Rails 中的 Action View 完全指南:模板、局部模板与布局

2026-09-07 14:33:13作者:彭桢灵Jeremy

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)中的 VAction 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_paramto_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 由三部分组合而成:

  1. .html.erb ERB 文件本身;
  2. 包裹它的 layout(布局)模板;
  3. ERB 文件可能引用的所有 partials(局部模板)。

下面分别详细介绍 TemplatesPartialsLayouts 三部分。

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

循环体用普通嵌入标签 <% %> 搭建,循环内输出姓名时用输出标签 <%= %>

需要注意,printputs 这类函数不会被渲染到 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 扩展名的模板会自动获得一个名为 xmlXmlMarkup 对象。基础示例:

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 &amp; 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_templatepartial_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_reviewsnil

<%# 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 ]) %>

不带 partiallocals 键的 render 简写

如果只需 partiallocals 两个选项,可以省略键名、只写值:

<%= 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 的路径与默认局部变量名。

asobject 选项

默认情况下,传入模板的对象会存放在与模板同名的局部变量中。所以:

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

它假定 @productsProduct 实例的集合。Rails 按约定从集合中模型的名字(这里是 Product)推导出使用的 partial。更有趣的是,这种简写甚至可以渲染由不同模型实例混合而成的集合——Rails 会为每个成员自动挑选正确的 partial。

源码印证:集合渲染实现在 actionview/lib/action_view/renderer/collection_renderer.rb。其中:

另外,当 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),它携带 indexsize,并提供 first?last? 便捷判断方法,_counter 正是 iterationindex 的向后兼容别名。

下面两节(Strict LocalsLocal 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 保留字(如 classif)同名的带默认值局部变量,可通过 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>&copy; <%= Date.current.year %> Your Company</p>
</footer>

在这个布局中,视图内容会渲染在 <%= yield %> 的位置,并被相同的 <head><nav><footer> 内容包围。该示例也演示了布局里常见的一批 helper:csrf_meta_tagscsp_meta_tagstylesheet_link_tagjavascript_importmap_tagslink_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 一起渲染。当前对象与对象计数变量(上例中的 articlearticle_counter)在布局内与在 partial 内一样可用。该逻辑同样实现在 collection_renderer.rb 的逐元素渲染中(locals[as] = objectlocals[counter] = indexlocals[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.rbtag.div 等通用标签)、form_helper.rbform_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.htmlpublic/404.de.html,即可获得本地化的错误处理页面。

更多细节参见 国际化(I18n)指南

小结与延伸阅读

总结一下本指南的核心结论:

  1. 职责边界:Action View 是 MVC 的 V,只负责把数据渲染为响应正文,与 Action Controller 解耦、也不依赖 Active Model,可在任何 Ruby 项目中独立使用。
  2. 模板三分.erb(HTML)、.jbuilder(JSON)、.builder(XML)三种模板系统由文件扩展名驱动;ERB 模板经由 Erubi 编译为写入输出缓冲区的 Ruby 方法。
  3. Partial 是视图复用的主力:从基础的 render "product",到 locals:/object:/as:/collection:/spacer_template: 等全套选项,再到 local_assigns、Ruby 3.1 模式匹配与 strict locals 签名,Action View 为局部模板提供了从入门到高性能的全部手段。
  4. Layout 保证页面骨架一致:按控制器名查找布局、回退 application.html.erb,partial 也可以套用自己的布局(可配合集合渲染)。
  5. 本地化视图:依赖 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 则验证模板编译相关机制),可作为阅读实现与学习行为边界的补充资料。

登录后查看全文
热门项目推荐
相关项目推荐