欢迎回到Ruby on Rails模式和反模式系列的第三篇文章。在之前的文章中,我们介绍了一般的模式和反模式,以及与Rails模型有关的模式。在这篇文章中,我们将讨论一些与Rails视图相关的模式和反模式。
Rails视图有时可以完美地工作,而且速度很快,而在其他时候,它们会有各种问题。如果你想增加对如何处理视图的信心,或者你只是想了解更多关于这个话题的信息,那么这篇博文就是为你准备的。让我们直接进入。
正如你可能知道的,Rails框架遵循惯例而不是配置。由于Rails非常重视模型-视图-控制器(MVC)模式,所以这句格言自然也适用于视图代码。这包括你的标记(ERB或Slim文件)、JavaScript和CSS文件。乍一看,你可能会认为视图层非常简单明了,但请记住,这些天来,视图层中存在着各种技术的混合。
我们在视图中使用JavaScript、HTML和CSS。这三者会导致代码的混乱和无序--导致从长远来看没有什么意义的实现。幸运的是,今天我们将通过Rails视图层的一些常见问题和解决方案。
强大的视图
这是一个并不经常发生的错误,但一旦发生,就会很碍眼。有时,人们倾向于把领域逻辑或查询直接放在视图中。这使得视图层承担了繁重的工作或动力。有趣的是,Rails实际上允许这种情况轻易发生。在这个问题上没有 "安全网",你可以在视图层做任何你想做的事情。
根据定义,MVC模式的视图层应该包含表现逻辑。它不应该被领域逻辑或数据查询所困扰。在Rails中,你可以得到ERB文件(Embedded Ruby),允许你写Ruby代码,然后将其评估为HTML。如果我们考虑一个在索引页上列出歌曲的网站的例子,那么视图逻辑将在app/views/songs/index.html.erb 。
为了说明什么是 "举重若轻 "以及什么是不能做的,让我们看一下下面的例子:
# app/views/songs/index.html.erb
<div class="songs">
<% Song.where(published: true).order(:title) do |song| %>
<section id="song_<%= song.id %>">
<span><%= song.title %></span>
<span><%= song.description %></span>
<a href="<%= song.download_url %>">Download</a>
</section>
<% end %>
</div>
这里的一个巨大的反模式是在标记中直接获取歌曲。获取数据的责任应该委托给控制器或从控制器中调用的服务。我有时看到人们在控制器中准备一些数据,然后在视图中获取更多数据。这是一个糟糕的设计,它使你的网站变得更慢,因为你经常用查询来强调你的数据库。
你应该做的是,从控制器动作中公开一个@songs 实例变量,并在标记中调用它,就像这样:
class SongsController < ApplicationController
...
def index
@songs = Song.all.where(published: true).order(:title)
end
...
end
# app/views/songs/index.html.erb
<div class="songs">
<% @songs.each do |song| %>
<section id="song_<%= song.id %>">
<span><%= song.title %></span>
<span><%= song.description %></span>
<a href="<%= song.download_url %>">Download</a>
</section>
<% end %>
</div>
这些例子远非完美。如果你想让你的控制器代码更易读,并避免SQL Pasta,我敦促你查看之前的博文。另外,把逻辑留在视图层会增加其他人试图在此基础上构建解决方案的机会。
利用Rails提供的东西
我们将在这里保持简短。作为一个框架,Ruby on Rails有很多整洁的助手,特别是在视图里面。这些灵巧的小助手可以让你快速而毫不费力地构建你的视图层。作为Rails的初级用户,你可能会想在你的ERb文件里面写上完整的HTML,就像这样:
# app/views/songs/new.html.erb
<form action="/songs" method="post">
<div class="field">
<label for="song_title">Title</label>
<input type="text" name="song[title]" id="song_title">
</div>
<div class="field">
<label for="song_description">Description</label>
<textarea name="song[description]" id="song_description"></textarea>
</div>
<div class="field">
<label for="song_download_url">Download URL</label>
<textarea name="song[download_url]" id="song_download_url"></textarea>
</div>
<input type="submit" name="commit" value="Create Song">
</form>
有了这个HTML,你应该得到一个漂亮的新歌表单,如下图所示:

但是,在Rails中,你不需要也不应该写这样的纯HTML,因为Rails就在那里支持你。你可以使用form_with 视图助手,它将为你生成HTML。form_with 是在Rails 5.1中引入的,它的存在是为了取代一些人可能熟悉的form_tag 和form_for 。让我们看看form_with 是如何将我们从编写额外代码中解脱出来的。
<%= form_with(model: song, local: true) do |form| %>
<div class="field">
<%= form.label :title %>
<%= form.text_field :title %>
</div>
<div class="field">
<%= form.label :description %>
<%= form.text_area :description %>
</div>
<div class="field">
<%= form.label :download_url do %>
Download URL
<% end %>
<%= form.text_area :download_url %>
</div>
<%= form.submit %>
<% end %>
除了为我们生成HTML之外,form_with 还会生成一个防止CSRF攻击的真实性令牌。因此,在几乎所有的情况下,你最好使用指定的助手,因为它们可能与Rails框架玩得很好。如果你试图提交一个普通的HTML表单,它将会失败,因为没有与请求一起提交有效的真实性令牌。
除了form_with 、label 、text_area 和submit 助手外,还有一堆 Rails 开箱即用的视图助手。它们的存在是为了让你的生活更轻松,你应该更好地了解它们。其中一个 "全明星 "无疑是link_to :
<%= link_to "Songs", songs_path %>
它将生成以下HTML:
<a href="/songs">Songs</a>
我不会对每个助手进行详细介绍,因为这篇文章太长了,而且浏览所有助手也不是今天的主题。我建议你通过Rails Action View帮助器指南,挑选你的网站需要的东西。
重用和组织视图代码
让我们想象一下完美的网络应用。在完美的使用案例中,没有if-else语句,只有纯粹的代码,从控制器中获取数据并将其放在HTML标签之间。这种应用也许存在于黑客马拉松和梦想中,但现实世界的应用在渲染视图时有一堆的分支和条件。
当显示页面的一部分的逻辑变得过于复杂时,你应该怎么做?你该从哪里着手?一个一般的答案是,也许可以找一个现代的JavaScript库或框架,并建立一些复杂的东西。但是,既然这篇文章是关于Rails视图的,让我们看看里面的选项。
后市场(自定义)帮助器
比方说,你想在一首歌曲下面显示一个行动呼吁(CTA)按钮。但是,有一个问题--一首歌曲可以有一个下载的URL,或者由于某种原因,它可能会丢失。我们可能会想写一些与下面类似的代码:
# app/views/songs/show.html.erb
...
<div class="song-cta">
<% if @song.download_url %>
<%= link_to "Download", download_url %>
<% else %>
<%= link_to "Subscribe to artists updates",
artist_updates_path(@song.artist) %>
<% end %>
</div>
...
如果我们把上面的例子作为一个孤立的呈现逻辑来看,它看起来并不坏,对吗?但是,如果有更多这样的条件性呈现,那么代码的可读性就会降低。这也增加了某些东西、某些地方不能正确呈现的机会,尤其是在条件较多的时候。
解决这些问题的方法之一是将它们提取到一个单独的辅助工具中。幸运的是,Rails为我们提供了一种方法,可以轻松地编写自定义的帮助器。在app/helpers ,我们可以创建一个SongsHelper ,像这样:
module SongsHelper
def song_cta_link
content_tag(:div, class: 'song-cta') do
if @song.download_url
link_to "Download", @song.download_url
else
link_to "Subscribe to artists updates",
artist_updates_path(@song.artist)
end
end
end
end
如果我们打开一首歌的表演页面,我们仍然会得到同样的结果。然而,我们可以使这个例子变得更好一些。在上面的例子中,我们使用了一个实例变量@song 。如果我们决定在一个@song 是nil 的地方使用这个帮助器,这可能就不能用了。所以为了切断实例变量形式的外部依赖,我们可以像这样向帮助器传递一个参数。
module SongsHelper
def song_cta_link(song)
content_tag(:div, class: 'song-cta') do
if song.download_url
link_to "Download", song.download_url
else
link_to "Subscribe to artists updates",
artist_updates_path(song.artist)
end
end
end
end
然后,在视图中,我们可以像下面这样调用帮助器:
# app/views/songs/show.html.erb
...
<%= song_cta_link(@song) %>
...
这样一来,我们在视图中应该会得到和之前一样的结果。使用帮助器的好处是,你可以为它们写测试,确保将来不会发生关于它们的回归。缺点是它们是全局定义的,你必须确保助手的名字在你的应用程序中是唯一的。
如果你不太喜欢编写Rails自定义助手,你可以选择使用Draper gem的视图模型模式。或者你可以在这里推出你自己的视图模型模式,这应该不是那么复杂。如果你的网络应用刚刚起步,我建议从编写自定义帮助器慢慢开始,如果这带来了痛苦,就转向其他解决方案。
干涸你的视图
当我开始使用Rails时,我真正喜欢的是它能够轻松地将你的标记干燥,这对我来说几乎是不可思议的。Rails为你提供了创建参数的能力--可重复使用的代码片断,你可以在任何地方包含它。例如,如果你在多个地方渲染歌曲,而你在多个文件中都有相同的代码,那么创建一个歌曲部分是有意义的。
比方说,你显示你的歌曲,如下图所示:
# app/views/songs/show.html.erb
<p id="notice"><%= notice %></p>
<p>
<strong>Title:</strong>
<%= @song.title %>
</p>
<p>
<strong>Description:</strong>
<%= @song.description %>
</p>
<%= song_cta_link %>
<%= link_to 'Edit', edit_song_path(@song) %> |
<%= link_to 'Back', songs_path %>
但是,你也想用同样的标记在另一个页面上显示它。那么你可以创建一个新的文件,其前缀为下划线,如app/views/songs/_song.html.erb :
# app/views/songs/_song.html.erb
<p>
<strong>Title:</strong>
<%= @song.title %>
</p>
<p>
<strong>Description:</strong>
<%= @song.description %>
</p>
<%= song_cta_link(@song) %>
然后在你想包括歌曲部分的任何地方,你只需做以下工作:
...
<%= render "song" %>
...
Rails会自动查询_song 部分是否存在,并将其呈现。与自定义帮助器的例子类似,我们最好在我们的局部中摆脱实例变量@song :
# app/views/songs/_song.html.erb
<p>
<strong>Title:</strong>
<%= song.title %>
</p>
<p>
<strong>Description:</strong>
<%= song.description %>
</p>
<%= song_cta_link(song) %>
然后,我们将需要把歌曲变量传递给局部,使其更可重复使用,并适合包含在其他地方:
...
<%= render "song", song: @song %>
...
最后的思考
这篇文章就讲到这里,朋友们。总结一下,我们经历了一些你在Rails视图领域可能遇到的模式和反模式。以下是一些经验之谈。
- 避免在UI中使用复杂的逻辑(不要让View做很多事情)。
- 了解Rails在视图辅助工具方面给你提供了什么。
- 用自定义的帮助器和参数来结构和重用你的代码。
- 不要过多地依赖实例变量。
在下一篇文章中,我们将介绍Rails控制器模式和反模式,在那里事情会变得非常混乱。敬请关注。
在下一篇文章之前,欢呼吧!