Ruby on Rails 局部渲染(Partial Render)

局部渲染是⼀种在 Rails 专案很常⾒的程序码整理⼿法之⼀。在前⼀个章节整理表单
的时候,有⽤到了这样的写法:

<%= render "form" %>
其实完整的写法是这样写:
<%= render partial: "form" %>

但⽤前⾯简单的写法在这边也是可以的。这⾏的意思是会去同⼀个⽬录找 _form
这个档案,并且把档案内容安插在这个地⽅。注意这个档案必须要是底线开头的,
否则会出现 ActionView::MissingTemplate 的错误讯息:

通常局部渲染适⽤於「可以重复使⽤」的程序码,像是在上个章节的表单就是⼀
例。

较好的设计

虽然 <%= render "form" %> 这样短短⼀⾏很容易⽤,但这样不⾒得是个好的设
计。⼀个容易重复使⽤的局部样版,不应该依赖满天⾶的实体变数。举个例⼦来
说,如果想要做⼀个叫做横幅广告的局部样版,可能会这样写(档案名称
_banner.html.erb ):

<div class="advertisement">
<div>广告</div>
<div> <%= @content %> </div>
</div>
使⽤的时候这样⽤:
<%= render "banner" %>

也就是说,这个 _banner.html.erb 会⾃⼰去空气中抓看看有没有 @content
这个实体变数给它,有的话就印出来。但事实上不是每个⾴⾯都有这东⻄可以抓,
所以不⾒得容易重复使⽤。比较建议的设计会是这样:

<div class="advertisement">
<div>广告</div>
<div> <%= content %> </div>
</div>
让这个局部样版里只有普通的区域变数,然後在使⽤它的时候,把值传主动传给
它:
<%= render partial: "banner", locals: {content: "我是广告的内容"} %>

注意:这样的写法 partial 参数不能省略。
这个局部样版就会得到⼀个 content 的区域变数。这样做似乎变得之前更⿇烦,
但这样的设计可以让这个局部样版变得更像⼀个「元件」,它被动的等着你喂它资
料,⽽不是⾃⼰伸⼿去空中抓,这样⼀来不管在哪个⾴⾯都可适⽤。
上⾯这个写法可以再精简成这样:

<%= render "banner", content: "我是广告的内容" %>

魔术 render
在上⼀章的「候选⼈列表」的程序码中,中间有⼀段 each 回圈,不断的印出资
料:

<h1>候选⼈列表</h1>
..[略]..
<tbody>
<% @candidates.each do |candidate| %>
<tr>
<td><%= link_to "投给这位", "#" %></td>
<td><%= candidate.name %>(年龄:<%= candidate.age %> 岁)</td>
<td><%= candidate.party %></td>
<td><%= candidate.politics %></td>
<td><%= candidate.votes %></td>
<td>
<%= link_to "编辑", edit_candidate_path(candidate) %>
15 Layout,Render 与 View Helper
232
<%= link_to "删除", candidate_path(candidate), method: "de
lete", data: { confirm: "确认删除" } %>
</td>
</tr>
<% end %>
</tbody>
</table>
这段如果使⽤局部样版来整理,可以把中间那段抽出来:
<h1>候选⼈列表</h1>
..[略]..
<tbody>
<%= render "candidate" %>
</tbody>
</table>
然後在 app/views/candidates ⽬录底下新增⼀个档案
_candidate.html.erb :
<% @candidates.each do |candidate| %>
<tr>
<td><%= link_to "投给这位", vote_candidate_path(candidate), metho
d: "post", data: { confirm: "确认要投给这位候选⼈吗?!" }, class: "btn
btn-danger btn-xs" %></td>
<td><%= candidate.name %>(年龄:<%= candidate.age %> 岁)</td>
<td><%= candidate.party %></td>
<td><%= candidate.politics %></td>
<td><%= candidate.votes %></td>
<td>
<%= link_to "编辑", edit_candidate_path(candidate) %>
<%= link_to "删除", candidate_path(candidate), method: "delete"
, data: { confirm: "确认删除" } %>
</td>
</tr>
<% end %>

这样的改法感觉没什麽了不起的,就是把原来的内容整个剪到另⼀个档案⽽已,它
需要的 @candidates 实体变数也还是⾃⼰往空中抓,前⾯才刚讲这样不是好的设
计。的确,如果只是这样的话其实也不需要改了。Rails 针对这样的⽤法,有提供
render ⽅法⼀个 collection 参数:

<%= render partial: "candidate", collection: @candidates %>

然後 _candidate.html.erb 档案的内容就可以把外层的 each 回圈拿掉,变成
这样:

<tr>
<td><%= link_to "投给这位", vote_candidate_path(candidate), metho
d: "post", data: { confirm: "确认要投给这位候选⼈吗?!" }, class:"btn
btn-danger btn-xs" %></td>
<td><%= candidate.name %>(年龄:<%= candidate.age %> 岁)</td>
<td><%= candidate.party %></td>
<td><%= candidate.politics %></td>
<td><%= candidate.votes %></td>
<td>
<%= link_to "编辑", edit_candidate_path(candidate) %>
<%= link_to "删除", candidate_path(candidate), method: "delete"
, data: { confirm: "确认删除" } %>
</td>
</tr>

因为传了 collection 参数的关系,在局部样版里即使拿掉了 each 回圈,它还
是可以正常运作,⽽且呈现的画⾯还是跟原来的⼀样。这样⼀来,在
_candidate.html.erb 档案里的都只剩区域变数,不需要再依赖空中的实体变
数。
⽽且 render ⽅法还可以再短⼀点,把这⼀⾏:

<%= render partial: "candidate", collection: @candidates %>

直接改成这样:

<%= render @candidates %>

_candidate.html.erb 档案不需要改,整个还是可以正常运作。短短⼀⾏,⽽且
还不⽤写回圈就可以达到跟原来⽤ each 回圈⼀样的效果,很神奇吧!
但这样的写法其实有点过於魔术,⽽且依赖不少 Rails 里的「惯例」。如果要让

<%= render @candidates %> 可以正常运作的话,需要完成以下几件事:
  1. 局部样版的档名必须是那包资料的「单数」,以上⾯这个例⼦来说,那包资料
    叫做 @candidates ,所以 Partial 的档名就是 _candidate.html.erb 。
  2. 局部样版档案必须⽽且放在对的位置,以这个例⼦来说,档案是放在
    app/views/candidates ⽬录里。
  3. 局部样版里不需要写回圈(写了反⽽会多跑⼀层回圈),里⾯⽤到的区域变数
    必须是单数,例如 candidate 。
    如果没有按照这些惯例就会出现找不到档案或路径的错误讯息。

参考资料

[为你自己学Ruby on Rails]https://railsbook.tw/chapters/08-ruby-basic-4.html


<<:  [Day21] 用 WASM 做一个凯萨密码 加密 / 解密 网站

>>:  [Day - 10] - 运用FlywayDB自动化整合Spring JPA 的模式注解之旅

[JS] You Don't Know JavaScript [this & Object Prototypes] - Object [番外 - getter/setter]

前言 我们在Object [下]中有提到 getter / setter,由於这个部分在书中的解释是...

TailwindCSS - 价目表卡片实战 - 登入弹窗开发

这次要来实作一个登入的弹窗效果,以前做弹窗大多是使用 Bootstrap 的弹窗元件或是 ligh...

[DAY12] 在 Azure Machine Learning 里 Label data(上)

DAY12 在 Azure Machine Learning 里 Label data(上) Azu...

解决venv中无法安装numpy的问题(Could not build wheels for numpy which use PEP 517)

懒人包 在venv下,执行 pip install --upgrade pip 当上述指令失败时,执...

JavaScript基本功修练:Day26 - Promise的语法糖:async/await

除了Promise之外,还有async/await语法来处理非同步程序,它背後的操作原理与Promi...