news 2026/9/8 22:38:52

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

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Ruby on Rails 中的 Action View 完全指南:模板、局部模板与布局

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_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.erbERB 文件本身;
  2. 包裹它的 layout(布局)模板;
  3. ERB 文件可能引用的所有 partials(局部模板)。

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

Templates(模板)

Action View 模板可以用多种格式编写,Rails 依据文件扩展名区分不同的模板系统:

扩展名渲染系统产出内容
.erbEmbedded RubyHTML 响应
.jbuilderJbuilder gemJSON 响应
.builderBuilder::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_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_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。其中:

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

下面两节(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 保留字(如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模板中,让_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 一起渲染。当前对象与对象计数变量(上例中的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 则验证模板编译相关机制),可作为阅读实现与学习行为边界的补充资料。

【免费下载链接】railsRuby on Rails项目地址: https://gitcode.com/GitHub_Trending/rai/rails

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/8 22:36:20

C#医院电子病历系统源码解析与二次开发实战指南

简介&#xff1a;一份基于C#的医院电子病历系统源码包&#xff0c;面向需要完成毕业设计或从事医疗信息系统开发的C#学习者&#xff0c;针对患者信息管理、病历记录、医生排班、药品追踪与报表统计等典型业务场景提供可直接借鉴的实现方案。压缩包整体约197.15MB&#xff0c;适…

作者头像 李华
网站建设 2026/9/8 22:36:04

Linux下Qt串口通信实战:从环境配置到粘包处理全解析

简介&#xff1a;面向需要在 Linux 环境下进行设备串口通信开发的 Qt 程序员&#xff0c;这套示例包以实际可编译的 Qt 工程为主线&#xff0c;讲解如何基于 /dev/ttySx 串口和 QSerialPort 模块完成端口配置、数据读写与事件处理。资源共 23 个文件&#xff0c;主要包含 cpp/h…

作者头像 李华
网站建设 2026/9/8 22:33:09

西门子S7-1500 PLC报价差异3倍的真相:配置陷阱与成本维度拆解

1. 为什么西门子PLC报价能差出3倍&#xff1f;这不是坑&#xff0c;是“配置陷阱”在说话 你手头刚收到三份西门子PLC的报价单&#xff1a;一份8万&#xff0c;一份15万&#xff0c;还有一份22万——型号都写着S7-1500&#xff0c;CPU型号也标得一模一样&#xff0c;都是6ES751…

作者头像 李华
网站建设 2026/9/8 22:32:40

硬件工程师招聘面试实战:笔试高频考点与学习路线全拆解

1. 岗位招新之前&#xff0c;先看清硬件工程师的真实画像这几天部门挂出硬件工程师的招聘岗位&#xff0c;简历收了不少&#xff0c;筛了一圈下来&#xff0c;真正能进入面试环节的其实寥寥无几。不是候选人能力不行&#xff0c;而是大多数人压根没搞清楚&#xff0c;硬件工程师…

作者头像 李华
网站建设 2026/9/8 22:32:35

健康医疗类APP的Android开发岗:技能模型、面试考点与项目实战

在移动开发里&#xff0c;健康医疗类APP一直是比较特殊的存在&#xff0c;而且随着互联网医疗平台、慢病管理、智能硬件数据打通这类业务越做越深&#xff0c;Android开发岗位的需求量也跟着起来了。和做电商、工具类应用不一样&#xff0c;健康医疗类APP对开发者的要求不只是“…

作者头像 李华