在线版的内容可能落后于电子书,如果想及时获得更新,请购买电子书

第 5 章 完善布局

第 4 章简介 Ruby 时,我们学习了如何在演示应用中引入样式表(4.1 节),不过现在样式表中还没有内容。本章将使用一个 CSS 框架,还会自己编写样式,填充样式表。[1]我们还将完善布局,添加指向各个页面的链接(例如首页和“关于”页面,参见 5.1 节)。在这个过程中,我们将学习局部视图、Rails 路由和 Asset Pipeline,还会介绍 Sass(5.2 节)。最后,我们还要向前迈出很重要的一步:允许用户在我们的网站中注册(5.4 节)。

本章大部分改动是添加和修改应用的布局,这些操作一般不由测试驱动,或者完全不用测试。所以我们大部分时间都在文本编辑器和浏览器中,只用 TDD 添加“联系”页面(5.3.1 节)。不过,我们将编写一种重要的测试,集成测试( integration test),检查最终完成的布局中有所需的链接(5.3.4 节)。

5.1 添加一些结构

本书介绍 Web 开发而不是 Web 设计,不过在一个看起来很简陋的应用中开发会让人提不起劲,所以本节要向布局中添加一些结构,再加入一些 CSS,实现基本的样式。除了使用自定义的 CSS 规则之外,我们还会使用由 Twitter 开发的开源 Web 设计框架 Bootstrap。我们会按照一定的方式组织代码——当布局文件中的内容变多以后,再使用局部视图清理。

开发 Web 应用时,尽早对用户界面有个统筹安排往往会对你有所帮助。在本书后续内容中,我会经常使用网页构思图(mockup,在 Web 领域经常称之为“线框图”),展示应用最终外观的草图。[2]本章大部分内容都是在开发 3.2 节编写的静态页面,我们要在页面中加入网站徽标、导航栏和网站页脚。在这些页面中,最重要的是“首页”,它的构思图如图 5.1 所示,图 5.9 是最终实现的效果。你会发现二者之间的某些细节有所不同,例如,在最终实现的页面中我们加入了 Rails 徽标——这没什么关系,因为构思图没必要画出每个细节。

home page mockup 3rd edition
图 5.1:演示应用首页的构思图

与之前一样,如果使用 Git 做版本控制,现在最好创建一个新分支:

$ git checkout -b filling-in-layout

5.1.2 Bootstrap 和自定义的 CSS

前一节为很多 HTML 元素指定了 CSS 类,这样我们就可以使用 CSS 灵活地构建布局了。如前所述,其中很多类在 Bootstrap 中有特殊的意义。Bootstrap 是 Twitter 开发的框架,可以方便地把精美的 Web 设计和用户界面元素添加到使用 HTML5 开发的应用中。本节,我们将结合 Bootstrap 和一些自定义的 CSS,为演示应用添加一些样式。值得注意的是,使用 Bootstrap 后,应用的设计就自动实现了响应式(responsive),在各种设备中都具有精美的外观。

首先,我们要安装 Bootstrap。在 Rails 应用中可以使用 bootstrap-sass 这个 gem,如代码清单 5.5 所示。Bootstrap 框架本身使用 Less 语言编写动态样式表,而 Rails 的 Asset Pipeline 默认支持的是(非常类似的)Sass 语言。bootstrap-sass 会把 Less 转换成 Sass,而且让 Bootstrap 中所有必要的文件都可以在当前应用中使用。[9]

代码清单 5.5:把 bootstrap-sass gem 添加到 Gemfile 文件中
source 'https://rubygems.org'

gem 'rails',                   '5.1.6'
gem 'sass-rails',              '5.0.6'
.
.
.

和之前一样,运行 bundle install 安装 Bootstrap:

$ bundle install

rails generate 命令会自动为控制器生成一个单独的 CSS 文件,但很难使用正确的顺序引入这些样式,所以简单起见,本书会把所有 CSS 都放在一个文件中。为此,我们要先新建这个 CSS 文件:

$ touch app/assets/stylesheets/custom.scss

(这里使用 3.3.3 节用过的 touch 命令,此外也可以使用其他方式。)目录名和文件扩展名都很重要。app/assets/stylesheets/ 目录是 Asset Pipeline 的一部分,其中所有的样式表都会引入 application.css 文件。文件名 custom.scss 中包含 .scss 扩展名,说明这是“Sassy CSS”文件,Asset Pipeline 会使用 Sass 处理其中的内容。(5.2 节才会使用 Sass,不过加入这个扩展名才能发挥 bootstrap-sass gem 的作用。)

在这个 CSS 文件中,我们可以使用 @import 函数引入 Bootstrap(以及相关的 Sprockets 代码),如代码清单 5.6 所示。[10]

代码清单 5.6:导入 Bootstrap 中的 CSS
app/assets/stylesheets/custom.scss
@import "bootstrap-sprockets";
@import "bootstrap";

这两行代码会引入整个 Bootstrap CSS 框架。然后,重启 Web 服务器(先按 Ctrl-C 键,然后执行 rails server 命令),让这些改动生效,效果如图 5.5 所示。文本的位置还不合适,徽标也没有任何样式,不过颜色搭配和注册按钮看起来都不错。

下面我们要加入一些整站都会用到的 CSS,美化网站布局和各个页面,如代码清单 5.7 所示。效果如图 5.6 所示。这段代码定义了很多样式规则,为了说明 CSS 规则的作用,我通常会加入一些 CSS 注释,放在 /* ... */ 中。

代码清单 5.7:添加全站使用的 CSS
app/assets/stylesheets/custom.scss
@import "bootstrap-sprockets";
@import "bootstrap";

/* universal */

body {
  padding-top: 60px;
}

section {
  overflow: auto;
}

textarea {
  resize: vertical;
}

.center {
  text-align: center;
}

.center h1 {
  margin-bottom: 10px;
}

注意,代码清单 5.7 中的 CSS 格式都是统一的。一般来说,CSS 规则通过类、ID、HTML 标签或者三者结合在一起来指代目标,然后在后面跟着一些样式声明。例如:

body {
  padding-top: 60px;
}

这个规则把页面的上内边距设为 60 像素。我们在 header 标签上指定了 navbar-fixed-top 类,Bootstrap 会把这个导航条固定在页面的顶部,所以页面的上内边距会把主内容区和导航条隔开一段距离。(导航条的颜色在 Bootstrap 2.0 中变了,所以要加入 navbar-inverse 类,把亮色变暗。)下面的 CSS 规则:

.center {
  text-align: center;
}

.center 类的样式定义为 text-align: center;.center 中的点号说明这个规则是样式化一个类。(在代码清单 5.9 中会看到,# 样式化一个 ID。)这个规则的意思是,任何类为 .center 的标签(例如 div),其中包含的内容都会在页面中居中显示。(代码清单 5.2 中有用到这个类。)

虽然 Bootstrap 提供了很精美的文字排版样式,我们还是要为文字的外观添加一些自定义的规则,如代码清单 5.8 所示。(并不是所有样式都用于“首页”,但所有规则都会在这个演示应用的某个地方用到。)效果如图 5.7 所示。

sample app only bootstrap 3rd edition
图 5.5:使用 Bootstrap CSS 后的演示应用
sample app universal 3rd edition
图 5.6:添加一些留白以及其他全局样式
代码清单 5.8:添加一些精美的文字排版样式
app/assets/stylesheets/custom.scss
@import "bootstrap-sprockets";
@import "bootstrap";
.
.
.
/* typography */

h1, h2, h3, h4, h5, h6 {
  line-height: 1;
}

h1 {
  font-size: 3em;
  letter-spacing: -2px;
  margin-bottom: 30px;
  text-align: center;
}

h2 {
  font-size: 1.2em;
  letter-spacing: -1px;
  margin-bottom: 30px;
  text-align: center;
  font-weight: normal;
  color: #777;
}

p {
  font-size: 1.1em;
  line-height: 1.7em;
}
sample app typography 3rd edition
图 5.7:添加一些排版样式

最后,我们还要为只包含“sample app”文本的网站徽标添加一些样式。代码清单 5.9 中的 CSS 把文字变成全大写字母,并修改字号、颜色和位置。(我们使用的是 ID,因为我们希望徽标在页面中只出现一次,不过也可以使用类。)

代码清单 5.9:添加网站徽标的样式
app/assets/stylesheets/custom.scss
@import "bootstrap-sprockets";
@import "bootstrap";
.
.
.
/* header */

#logo {
  float: left;
  margin-right: 10px;
  font-size: 1.7em;
  color: #fff;
  text-transform: uppercase;
  letter-spacing: -1px;
  padding-top: 9px;
  font-weight: bold;
}

#logo:hover {
  color: #fff;
  text-decoration: none;
}

其中,color: #fff; 把徽标文字的颜色变成白色。HTML 中的颜色代码由 3 组 16 进制数组成,分别代表三原色中的红绿蓝(就是这个顺序)。#ffffff 是 3 种颜色都为最大值的情况,表示纯白色。#fff#ffffff 的简写形式。CSS 标准为很多常用的 HTML 颜色定义了别名,例如 white 代表 #fff。添加代码清单 5.9 中的样式后,效果如图 5.8 所示。

练习
  1. 使用代码清单 5.10 中的代码,把前一节练习中添加的猫图注释掉。使用 Web 审查工具确认页面的 HTML 源码中没有那个图像了。

  2. 代码清单 5.11 中的 CSS 添加到 custom.scss 文件中,隐藏应用中的所有图像(目前只有首页有一张 Rails 徽标)。使用 Web 审查工具确认图像确实不见了,但是 HTML 仍在。

代码清单 5.10:把嵌入式 Ruby 代码注释掉
<%#= image_tag("kitten.jpg", alt: "Kitten") %>
代码清单 5.11:隐藏图像的 CSS
img {
  display: none;
}

5.1.3 局部视图

虽然代码清单 5.1 中的布局达到了目的,但其中的内容看起来有点混乱。HTML shim 就占了三行,而且使用了只针对 IE 的奇怪句法,如果能把它打包放在一个单独的地方就好了。此外,页头的 HTML 自成一个逻辑单元,也可以把这部分打包放在某个地方。在 Rails 中我们可以使用局部视图(partial)实现这种想法。先来看一下定义了局部视图之后的布局文件,如代码清单 5.12 所示。

代码清单 5.12:把 HTML shim 和页头放到局部视图中之后的网站布局
app/views/layouts/application.html.erb
<!DOCTYPE html>
<html>
  <head>
    <title><%= full_title(yield(:title)) %></title>
    <%= csrf_meta_tags %>
    <%= stylesheet_link_tag    'application', media: 'all',
                                              'data-turbolinks-track': 'reload' %>
    <%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %>
    <%= render 'layouts/shim' %>
  </head>
  <body>
    <%= render 'layouts/header' %>
    <div class="container">
      <%= yield %>
    </div>
  </body>
</html>

在这段代码中,我们把 HTML shim 删掉,换成了一行代码,调用 Rails 的辅助方法 render

<%= render 'layouts/shim' %>

这行代码会寻找一个名为 app/views/layouts/_shim.html.erb 的文件,执行其中的代码,然后把结果插入视图。[11](回顾一下,执行 Ruby 表达式并将结果插入模板中要使用 <%= …​ %>。)注意,文件名 _shim.html.erb 的开头有个下划线,这是局部视图的命名约定,以便在目录中快速定位所有局部视图。

当然,若要局部视图起作用,我们要写入相应的内容。HTML shim 局部视图只包含三行代码,如代码清单 5.13 所示。

代码清单 5.13:HTML shim 局部视图
app/views/layouts/_shim.html.erb
<!--[if lt IE 9]>
  <script src="//cdnjs.cloudflare.com/ajax/libs/html5shiv/r29/html5.min.js">
  </script>
<![endif]-->

类似地,我们可以把页头移入局部视图,如代码清单 5.14 所示,然后再次调用 render 把这个局部视图插入布局中。(一般都要在文本编辑器中手动创建局部视图对应的文件。)

代码清单 5.14:网站页头的局部视图
app/views/layouts/_header.html.erb
<header class="navbar navbar-fixed-top navbar-inverse">
  <div class="container">
    <%= link_to "sample app", '#', id: "logo" %>
    <nav>
      <ul class="nav navbar-nav navbar-right">
        <li><%= link_to "Home",   '#' %></li>
        <li><%= link_to "Help",   '#' %></li>
        <li><%= link_to "Log in", '#' %></li>
      </ul>
    </nav>
  </div>
</header>

现在我们已经知道怎么创建局部视图了,让我们来加入与页头对应的页脚吧。你或许已经猜到了,我们会把这个局部视图命名为 _footer.html.erb,放在 layouts 目录中,如代码清单 5.15 所示。[12]

与页头类似,我们在页脚使用 link_to 创建指向“关于”页面和“联系”页面的链接,地址先使用占位符 #。(与 header 一样,footer 也是 HTML5 新增的标签。)

按照 HTML shim 和页头局部视图的方式,我们可以在布局视图中渲染页脚局部视图,如代码清单 5.16 所示。

当然,如果没有样式的话,页脚还很丑。页脚的样式参见代码清单 5.17,效果如图 5.9 所示。

练习
  1. 把 Rails 在 head 元素中插入的标签替换成代码清单 5.18 中的 render 调用。提示:简单起见,不要删除,应该剪切。

  2. 我们还没创建代码清单 5.18 所需的局部视图,所以测试应该失败。确认的确如此。

  3. layouts 目录中创建所需的局部视图,把内容粘贴进去,然后确认测试可以通过。

代码清单 5.18:把 Rails 在 head 元素中插入的标签替换成 render 调用
app/views/layouts/application.html.erb
<!DOCTYPE html>
<html>
  <head>
    <title><%= full_title(yield(:title)) %></title>
    <%= render 'layouts/rails_default' %>
    <%= render 'layouts/shim' %>
  </head>
  <body>
    <%= render 'layouts/header' %>
    <div class="container">
      <%= yield %>
      <%= render 'layouts/footer' %>
    </div>
  </body>
</html>

5.2 Sass 和 Asset Pipeline

Rails 中最有用的功能之一是 Asset Pipeline,它极大地简化了静态资源文件(CSS、JavaScript 和图像)的生成和管理。本节先概述 Asset Pipeline 的作用,然后说明如何使用 Sass 这个强大的 CSS 编写工具。

5.2.1 Asset Pipeline

Rails 开发者要理解 Asset Pipeline 的三个概念:静态资源目录,清单文件,以及预处理器引擎。[13]下面一一介绍。

静态资源目录

Rails 的 Asset Pipeline 使用三个标准的目录存放静态资源文件,各有各的作用:

  • app/assets:当前应用的静态资源文件;

  • lib/assets:开发团队自己开发的代码库使用的静态资源文件;

  • vendor/assets:第三方代码库使用的静态资源文件;

这几个目录中都有针对不同静态资源类型的子目录,例如:

$ ls app/assets/
images/ javascripts/ stylesheets/

现在我们知道 5.1.2 节custom.scss 存放位置的用意了:因为 custom.scss 只在应用中使用,所以把它放在 app/assets/stylesheets 目录中。

清单文件

把静态资源文件放在适当的目录中之后,要通过清单文件(manifest file)告诉 Rails 怎么把它们合并成一个文件(通过 Sprockets gem 实现,而且只合并 CSS 和 JavaScript 文件,不会合并图像)。举个例子,我们来看一下应用默认的样式清单文件,如代码清单 5.19 所示。

代码清单 5.19:应用的CSS清单文件
app/assets/stylesheets/application.css
/*
 * This is a manifest file that'll be compiled into application.css, which
 * will include all the files listed below.
 *
 * Any CSS and SCSS file within this directory, lib/assets/stylesheets,
 * vendor/assets/stylesheets, or vendor/assets/stylesheets of plugins, if any,
 * can be referenced here using a relative path.
 *
 * You're free to add application-wide styles to this file and they'll appear
 * at the bottom of the compiled file so the styles you add here take
 * precedence over styles defined in any styles defined in the other CSS/SCSS
 * files in this directory. It is generally better to create a new file per
 * style scope.
 *
 *= require_tree .
 *= require_self
 */

这里关键的代码是几行 CSS 注释,Sprockets 通过这些注释引入相应的文件:

/*
 .
 .
 .
 *= require_tree .
 *= require_self
*/

其中

*= require_tree .

会把 app/assets/stylesheets 目录中的所有 CSS 文件(包含子目录中的文件)都引入应用的 CSS 文件。

下面这行:

*= require_self

会把 application.css 这个文件中的 CSS 也加载进来。

Rails 提供的默认清单文件可以满足我们的需求,所以本书不会对其做任何修改。Rails 指南中有一篇专门介绍 Asset Pipeline 的文章,说得更详细。

预处理器引擎

准备好静态资源文件后,Rails 会使用一些预处理器引擎来处理它们,并通过清单文件将其合并,然后发送给浏览器。我们通过扩展名告诉 Rails 使用哪个预处理器。三个最常用的扩展名是:Sass 文件的 .scss,CoffeeScript 文件的 .coffee,ERb 文件的 .erb。我们在 3.4.3 节介绍过 ERb,5.2.2 节会介绍 Sass。本书不会使用 CoffeeScript,这是一门很小巧的语言,可以编译成浏览器中执行的 JavaScript。

预处理器引擎可以连接在一起使用,因此 foobar.js.coffee 只会使用 CoffeeScript 处理器,而 foobar.js.erb.coffee 会使用 CoffeeScript 和 ERb 处理器(按照扩展名的顺序从右向左处理,所以 CoffeeScript 处理器先执行)。

在生产环境中的效率问题

Asset Pipeline 带来的好处之一是,能自动优化静态资源文件,在生产环境中使用效果极佳。CSS 和 JavaScript 的传统组织方式是,把不同功能的代码放在不同的文件中,而且排版良好(有很多缩进)。这么做对编程人员很友好,但在生产环境中使用却效率低下——加载大量的文件会明显增加页面的加载时间,这是影响用户体验的最主要因素之一。使用 Asset Pipeline,生产环境中应用的所有样式都集中到一个 CSS 文件中(application.css),所有 JavaScript 代码都集中到一个 JavaScript 文件中(application.js),而且还会简化(minify)这些文件,删除不必要的空格,减小文件大小。这样我们就最好地平衡了两方面的需求——开发方便,线上高效。

5.2.2 句法强大的样式表

Sass 是一种编写 CSS 的语言,从多方面增强了 CSS 的功能。本节我们要介绍两个最主要的功能:嵌套和变量。(还有一个功能是“混入”,7.1.1 节再介绍。)

5.1.2 节简单说过,Sass 支持一种名为 SCSS 的格式(扩展名为 .scss),这是 CSS 的一个严格超集。SCSS 只为 CSS 添加了一些功能,而没有定义全新的句法。[14]也就是说,所有有效的 CSS 文件都是有效的 SCSS 文件,这对已经定义了样式的项目来说是件好事。在我们的应用中,因为要使用 Bootstrap,所以从一开始就使用了 SCSS。Rails 的 Asset Pipeline 会自动使用 Sass 预处理器处理扩展名为 .scss 的文件,所以 custom.scss 文件会首先经由 Sass 预处理器处理,然后引入应用的样式表中,再发送给浏览器。

嵌套

样式表中经常会定义嵌套元素的样式,例如,在代码清单 5.7 中,我们定义了 .center.center h1 两个样式:

.center {
  text-align: center;
}

.center h1 {
  margin-bottom: 10px;
}

使用 Sass 可将其改写成

.center {
  text-align: center;
  h1 {
    margin-bottom: 10px;
  }
}

内层的 h1 会自动放入 .center 上下文中。

嵌套还有一种形式,句法稍有不同。在代码清单 5.9 中,有如下的代码

#logo {
  float: left;
  margin-right: 10px;
  font-size: 1.7em;
  color: #fff;
  text-transform: uppercase;
  letter-spacing: -1px;
  padding-top: 9px;
  font-weight: bold;
}

#logo:hover {
  color: #fff;
  text-decoration: none;
}

其中徽标的 ID #logo 出现了两次,一次单独出现,另一次和 hover 伪类一起出现(鼠标悬停其上时的样式)。如果要嵌套第二组规则,我们要引用父级元素 #logo。在 SCSS 中,这使用 & 符号实现:

#logo {
  float: left;
  margin-right: 10px;
  font-size: 1.7em;
  color: #fff;
  text-transform: uppercase;
  letter-spacing: -1px;
  padding-top: 9px;
  font-weight: bold;
  &:hover {
    color: #fff;
    text-decoration: none;
  }
}

把 SCSS 转换成 CSS 时,Sass 会把 &:hover 编译成 #logo:hover

这两种嵌套方式都可以用在代码清单 5.17 中的页脚样式上,将其改写成:

footer {
  margin-top: 45px;
  padding-top: 5px;
  border-top: 1px solid #eaeaea;
  color: #777;
  a {
    color: #555;
    &:hover {
      color: #222;
    }
  }
  small {
    float: left;
  }
  ul {
    float: right;
    list-style: none;
    li {
      float: left;
      margin-left: 15px;
    }
  }
}

自己动手改写代码清单 5.17 是个不错的练习,改完后应该验证一下 CSS 是否还能正常使用。

变量

Sass 允许自定义变量来避免重复,这样也可以写出更具表现力的代码。例如,代码清单 5.8代码清单 5.17 中重复使用了同一个颜色代码:

h2 {
  .
  .
  .
  color: #777;
}
.
.
.
footer {
  .
  .
  .
  color: #777;
}

上面代码中的 #777 是淡灰色,我们可以把它定义成一个变量:

$light-gray: #777;

然后可以这样写 SCSS:

$light-gray: #777;
.
.
.
h2 {
  .
  .
  .
  color: $light-gray;
}
.
.
.
footer {
  .
  .
  .
  color: $light-gray;
}

因为像 $light-gray 这样的变量名比 #777 意思更明确,所以把不重复使用的值定义成变量往往也是很有用的。其实,Bootstrap 框架定义了很多颜色变量,Bootstrap 文档中有这些变量的 Less 形式。这个页面中的变量使用 Less 句法,而不是 Sass,不过 bootstrap-sass gem 为我们提供了对应的 Sass 形式。二者之间的对应关系也不难猜测,Less 使用 @ 符号定义变量,而 Sass 使用 $ 符号。在 Bootstrap 文档中我们看到已经为淡灰色定义了变量:

@gray-light: #777;

也就是说,在 bootstrap-sass gem 中有一个对应的 SCSS 变量 $gray-light。我们可以用它换掉自己定义的 $light-gray 变量:

h2 {
  .
  .
  .
  color: $gray-light;
}
.
.
.
footer {
  .
  .
  .
  color: $gray-light;
}

使用 Sass 提供的嵌套和变量功能改写应用的整个样式表后得到的代码如代码清单 5.20 所示。这段代码使用了 Sass 变量(参照 Bootstrap Less 变量页面)和内置的颜色名称(例如,white 代表 #fff)。请特别留意 footer 标签样式的改进有多明显。

代码清单 5.20:使用嵌套和变量改写后的 SCSS 文件
app/assets/stylesheets/custom.scss
@import "bootstrap-sprockets";
@import "bootstrap";

/* mixins, variables, etc. */

$gray-medium-light: #eaeaea;

/* universal */

body {
  padding-top: 60px;
}

section {
  overflow: auto;
}

textarea {
  resize: vertical;
}

.center {
  text-align: center;
  h1 {
    margin-bottom: 10px;
  }
}

/* typography */

h1, h2, h3, h4, h5, h6 {
  line-height: 1;
}

h1 {
  font-size: 3em;
  letter-spacing: -2px;
  margin-bottom: 30px;
  text-align: center;
}

h2 {
  font-size: 1.2em;
  letter-spacing: -1px;
  margin-bottom: 30px;
  text-align: center;
  font-weight: normal;
  color: $gray-light;
}

p {
  font-size: 1.1em;
  line-height: 1.7em;
}


/* header */

#logo {
  float: left;
  margin-right: 10px;
  font-size: 1.7em;
  color: white;
  text-transform: uppercase;
  letter-spacing: -1px;
  padding-top: 9px;
  font-weight: bold;
  &:hover {
    color: white;
    text-decoration: none;
  }
}

/* footer */

footer {
  margin-top: 45px;
  padding-top: 5px;
  border-top: 1px solid $gray-medium-light;
  color: $gray-light;
  a {
    color: $gray;
    &:hover {
      color: $gray-darker;
    }
  }
  small {
    float: left;
  }
  ul {
    float: right;
    list-style: none;
    li {
      float: left;
      margin-left: 15px;
    }
  }
}

Sass 提供了很多简化样式表的功能,代码清单 5.20 只用到了最主要的功能,这是个好的开始。更多功能请查看 Sass 的网站

练习
  1. 按照 5.2 节的建议,自己动手把代码清单 5.17 中页脚的 CSS 改成代码清单 5.20 中的 SCSS。

5.4 用户注册:第一步

为了完成本章的目标,本节要设置“注册”页面的路由,为此要创建第二个控制器。这是允许用户注册重要的第一步,我们将在第 6 章完成第二步,创建 User 模型,第 7 章完成整个功能。

5.4.1 Users 控制器

我们在 3.2 节创建了第一个控制器——StaticPages 控制器。现在要创建第二个,Users 控制器。和之前一样,我们使用 generate 命令创建所需的控制器骨架,并且指定用户注册页面所需的动作。遵照 Rails 使用的 REST 架构约定,我们把这个动作命名为 new。把 new 作为参数传给 generate 命令就可以自动创建这个动作,如代码清单 5.38 所示。

代码清单 5.38:生成 Users 控制器(包含 new 动作)
$ rails generate controller Users new
      create  app/controllers/users_controller.rb
       route  get 'users/new'
      invoke  erb
      create    app/views/users
      create    app/views/users/new.html.erb
      invoke  test_unit
      create    test/controllers/users_controller_test.rb
      invoke  helper
      create    app/helpers/users_helper.rb
      invoke    test_unit
      invoke  assets
      invoke    coffee
      create      app/assets/javascripts/users.coffee
      invoke    scss
      create      app/assets/stylesheets/users.scss

上述命令会创建我们需要的 Users 控制器,以及其中的 new 动作(代码清单 5.39)和一个占位视图(代码清单 5.40)。除此之外还会为新建用户页面生成一个简单的测试(代码清单 5.41)。

代码清单 5.39:默认生成的 Users 控制器,包含 new 动作
app/controllers/users_controller.rb
class UsersController < ApplicationController

  def new
  end
end
代码清单 5.40:默认生成的 new 视图
app/views/users/new.html.erb
<h1>Users#new</h1>
<p>Find me in app/views/users/new.html.erb</p>
代码清单 5.41:新建用户页面的测试 GREEN
test/controllers/users_controller_test.rb
require 'test_helper'

class UsersControllerTest < ActionDispatch::IntegrationTest

  test "should get new" do
    get users_new_url
    assert_response :success
  end
end

现在,测试应该能通过:

代码清单 5.42GREEN
$ rails test
练习
  1. 根据表 5.1,把代码清单 5.41 中的 users_new_url 换成 signup_path

  2. 前一题使用的路由还不存在,确认测试无法通过。(这么做是为了让你增强对测试驱动开发中“遇红-变绿”循环的理解。我们会在 5.4.2 节让测试通过。)

5.4.2 “注册”页面的 URL

有了前一节生成的代码,现在就可以通过 /users/new 访问新建用户页面。但是参照表 5.1,我们希望这个页面的 URL 是 /signup。为此,我们要参照代码清单 5.27 中的做法,为“注册”页面添加 get '/signup' 规则,如代码清单 5.43 所示。

代码清单 5.43:“注册”页面的路由 RED
config/routes.rb
Rails.application.routes.draw do
  root 'static_pages#home'
  get  '/help',    to: 'static_pages#help'
  get  '/about',   to: 'static_pages#about'
  get  '/contact', to: 'static_pages#contact'
  get  '/signup',  to: 'users#new'
end

然后,我们要更新前面生成的测试,使用注册页面的新路由,如代码清单 5.44 所示。

代码清单 5.44:更新 Users 控制器测试,使用注册页面的新路由 GREEN
test/controllers/users_controller_test.rb
require 'test_helper'

class UsersControllerTest < ActionDispatch::IntegrationTest

  test "should get new" do
    get signup_path
    assert_response :success
  end
end

接下来,我们使用具名路由让首页中的按钮指向正确的地址。与其他路由一样,添加 get '/signup' 后会得到具名路由 signup_path。我们在代码清单 5.45 中使用这个具名路由。针对“注册”页面的测试留作练习

最后,编写“注册”页面的临时视图,如代码清单 5.46 所示。

代码清单 5.46:“注册”页面的(临时)视图
app/views/users/new.html.erb
<% provide(:title, 'Sign up') %>
<h1>Sign up</h1>
<p>This will be a signup page for new users.</p>

现在,我们暂别链接和具名路由,到第 8 章再添加“登录”页面的路由。新创建的用户注册页面(/signup)如图 5.11 所示。

new signup page 4th edition
图 5.11:/signup 地址上的“注册”页面
练习
  1. 如果你没做前一节的练习,先修改代码清单 5.41 中的测试,使用具名路由 signup_path。因为我们在代码清单 5.43 中定义了那个路由,所以测试可以通过。

  2. 为了确认前一题的测试是正确的,先把 signup 路由注释掉,看测试是否失败,然后把注释去掉,看测试是否通过。

  3. 代码清单 5.32 中的集成测试里添加代码,使用 get 方法访问注册页面,确认页面的标题是正确的。提示:使用代码清单 5.36 中的 full_title 辅助方法。

5.5 小结

本章,我们为应用定义了一些样式,还设置了几个路由。本书剩下的内容会不断为这个应用添加功能:先添加用户注册、登录和退出功能,然后实现发微博功能,最后添加关注用户功能。

现在,如果使用 Git 的话,应该把本章所做的改动合并到主分支中:

$ git add -A
$ git commit -m "Finish layout and routes"
$ git checkout master
$ git merge filling-in-layout

然后推送到 Bitbucket 中(安全起见,先运行测试组件):

$ rails test
$ git push

最后,部署到 Heroku 中:

$ git push heroku

部署完成后应该在生产服务器中有一个可以正常运行的演示应用,如图 5.12 所示。

layout production
图 5.12:运行在生产环境中的演示应用

5.5.1 本章所学

  • 使用 HTML5 可以定义一个包括徽标、页头、页脚和主体内容的网站布局;

  • 为了用起来方便,可以使用 Rails 局部视图把部分结构放到单独的文件中;

  • 在 CSS 中可以使用类和 ID 编写样式;

  • Bootstrap 框架能快速实现设计精美的网站;

  • 使用 Sass 和 Asset Pipeline 能去除 CSS 中的重复,还能打包静态资源文件,提高在生产环境中的使用效率;

  • 在 Rails 中可以自己定义路由规则,得到具名路由;

  • 集成测试能高效模拟浏览器中的点击操作。

  1. 感谢读者 Colm Tuite 帮忙把原来的演示应用用 Bootstrap CSS 框架重写。
  2. 书中所有构思图都是使用 Mockingbird 这个在线应用制作的。
  3. 做这种用途时,shim 和 shiv 两个词可以互换使用。前者是正式的术语,根据词典的释义,它的意思是“用于对齐、整平或减少零件磨碎的垫片或薄片”,而后者(意思是“用作武器的小刀或剃刀”)显然是对 HTML5 shim 原作者的姓名(Sjoerd Visscher)玩弄的文字游戏。
  4. CSS 类和 Ruby 中的类完全没有关系。
  5. 你看到的空格数量可能有所不同,这没关系,因为空白在 HTML 中没有特殊意义(3.4.1 节说过)。
  6. 这个图像其实 Rails 以前的徽标,现在用的徽标是 SVG 格式,不太符合我们的需求。
  7. 你可能会注意到,img 标签的格式不是 <img>…​</img>,而是 <img …​ />。这种标签叫做“自关闭标签”(self-closing tag)。
  8. 图像来源:https://www.flickr.com/photos/deborah_s_perspective/14144861329。Copyright © 2009 by Deborah。未经改动,基于“知识共享 署名 2.0 通用”许可证使用。
  9. 在 Asset Pipeline 中也可以使用 Less,详见 less-rails-bootstrap gem。
  10. 如果你觉得这一步很难理解,先照做吧,我完全是按照这个 gem 的安装说明做的。
  11. 很多 Rails 开发者使用 shared 目录存放在不同视图中共用的局部视图。我倾向于在 shared 目录中存放辅助的局部视图,而把每个页面中都会用到的局部视图放在`layouts` 目录中。(我们会在第 7 章创建 shared 目录。)在我看来,这种方式比较符合逻辑,不过,都放在 shared 目录里也完全可行。
  12. 你可能想知道为什么要使用 footer 标签和 .footer 类。理由是,这样的标签对于人类来说更容易理解,而且 Bootstrap 也使用这个类名。把 footer 标签换成 div 标签也可以。
  13. 本节内容根据 Michael Erasmus 写的文章“The Rails 3 Asset Pipeline in (about) 5 Minutes”组织。更多信息参见 Rails 指南中的“Asset Pipeline”一文。
  14. Sass 仍然支持以前的 .sass 格式,这个格式相对来说更简洁,花括号更少,但是对现有项目不太友好,已经熟悉 CSS 的人学习难度也相对更大。
  15. 有些开发者坚持不在一个测试中编写多个断言。我觉得这种习惯会把问题变得复杂,而且,如果每个测试运行前都要设置一样的背景,就要编写更多的代码,所以完全没必要这么做。一个好的测试应该讲述连贯的故事,把测试分成多个片段会打断叙述过程。因此,我十分倾向于在一个测试中编写多个断言,让 Ruby(通过 MiniTest)告诉我到底哪一个断言失败了。