Ruby on Rails 中的 Action View 完全指南:模板、局部模板与布局
【免费下载链接】railsRuby on Rails项目地址: https://gitcode.com/GitHub_Trending/rai/rails
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.erbAction 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模板中,让_articlepartial 套上box布局渲染:
<%# app/views/articles/show.html.erb %> <%= render partial: 'article', layout: 'box', locals: { article: @article } %>box布局只是把_articlepartial 包进一个div:
<%# app/views/articles/_box.html.erb %> <div class="box"> <%= yield %> </div>注意:partial 布局能够访问传给render调用的局部变量article(虽然这个例子中_box.html.erb没有用到它)。
与全局布局不同,partial 布局的文件名仍然带有下划线前缀。
除了yield,也可以在 partial 布局中直接渲染一段代码块。例如没有_articlepartial 时,可以这样:
<%# app/views/articles/show.html.erb %> <%= render(layout: 'box', locals: { article: @article }) do %> <div> <p><%= article.body %></p> </div> <% end %>若沿用上面同一个_boxpartial,这段代码与前一个示例产生相同输出。
源码印证: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 则验证模板编译相关机制),可作为阅读实现与学习行为边界的补充资料。
【免费下载链接】railsRuby on Rails项目地址: https://gitcode.com/GitHub_Trending/rai/rails
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考