# 3.4 有点动态内容的页面
我们已经为一些静态页面创建了动作和视图,现在要稍微添加一些动态内容,根据所在的页面不同而变化:我们要让标题根据页面的内容变化。改变标题到底算不算真正动态还有争议,这么做能为[第 7 章](chapter7.html#sign-up)实现的真正动态内容打下基础。
我们的计划是修改首页、“帮助”页面和“关于”页面,让每页显示的标题都不一样。为此,我们要在页面的视图中使用 `<title>` 标签。大多数浏览器都会在浏览器窗口的顶部显示标题中的内容,而且标题对“搜索引擎优化”(Search-Engine Optimization,简称 SEO)也有好处。我们要使用完整的“遇红-变绿-重构”循环:先为页面的标题编写一些简单的测试(**遇红**),然后分别在三个页面中添加标题(**变绿**),最后使用布局文件去除重复内容(重构)。本节结束时,三个静态页面的标题都会变成“<页面的名字> | Ruby on Rails Tutorial Sample App”这种形式([表 3.2](#table-static-pages))。
`rails new` 命令会创建一个布局文件,不过现在最好不用。我们重命名这个文件:
```
$ mv app/views/layouts/application.html.erb layout_file # 临时移动
```
在真实的应用中你不需要这么做,不过没有这个文件能让你更好地理解它的作用。
表 3.2:演示应用中基本上是静态内容的页面
| 页面 | URL | 基本标题 | 变动部分 |
| --- | --- | --- | --- |
| 首页 | /static_pages/home | `"Ruby on Rails Tutorial Sample App"` | `"Home"` |
| 帮助 | /static_pages/help | `"Ruby on Rails Tutorial Sample App"` | `"Help"` |
| 关于 | /static_pages/about | `"Ruby on Rails Tutorial Sample App"` | `"About"` |
## 3.4.1 测试标题(遇红)
添加标题之前,我们要学习网页的一般结构,如[代码清单 3.21](#listing-html-structure) 所示。
##### 代码清单 3.21:网页一般的 HTML 结构
```
<!DOCTYPE html>
<html>
<head>
<title>Greeting</title>
</head>
<body>
<p>Hello, world!</p>
</body>
</html>
```
这段代码的最顶部是“文档类型声明”(document type declaration,简称 doctype),告诉浏览器使用哪个 HTML 版本(本例使用 [HTML5](http://en.wikipedia.org/wiki/HTML5))[[8](#fn-8)]。随后是 `head` 部分,包含一个 `title` 标签,其中的内容是“Greeting”。然后是 `body` 部分,包含一个 `p` 标签(段落),其中的内容是“Hello, world!”。(内容的缩进是可选的,HTML 不会特别对待空白,制表符和空格都会被忽略,但缩进可以让文档结构更清晰。)
我们要使用 `assert_select` 方法分别为[表 3.2](#table-static-pages) 中的每个标题编写简单的测试,合并到[代码清单 3.13](#listing-about-test) 的测试中。`assert_select` 方法的作用是检查有没有指定的 HTML 标签。这种方法有时也叫“选择符”,从方法名可以看出这一点。[[9](#fn-9)]
```
assert_select "title", "Home | Ruby on Rails Tutorial Sample App"
```
这行代码的作用是检查有没有 `<title>` 标签,以及其中的内容是不是字符串“Home | Ruby on Rails Tutorial Sample App”。把这样的代码分别放到三个页面的测试中,得到的结果如[代码清单 3.22](#listing-title-tests) 所示。
##### 代码清单 3.22:加入标题测试后的静态页面控制器测试 RED
test/controllers/static_pages_controller_test.rb
```
require 'test_helper'
class StaticPagesControllerTest < ActionController::TestCase
test "should get home" do
get :home
assert_response :success
assert_select "title", "Home | Ruby on Rails Tutorial Sample App" end
test "should get help" do
get :help
assert_response :success
assert_select "title", "Help | Ruby on Rails Tutorial Sample App" end
test "should get about" do
get :about
assert_response :success
assert_select "title", "About | Ruby on Rails Tutorial Sample App" end
end
```
(如果你觉得在标题中重复使用“Ruby on Rails Tutorial Sample App”不妥,可以看一下 [3.6 节](#mostly-static-pages-exercises)的练习。)
写好测试之后,应该确认一下现在测试组件是失败的(**RED**):
##### 代码清单 3.23:**RED**
```
$ bundle exec rake test
3 tests, 6 assertions, 3 failures, 0 errors, 0 skips
```
## 3.4.2 添加页面标题(变绿)
现在,我们要为每个页面添加标题,让前一节的测试通过。参照[代码清单 3.21](#listing-html-structure) 中的 HTML 结构,把[代码清单 3.9](#listing-custom-home-page) 中的首页内容换成[代码清单 3.24](#listing-home-view-full-html) 中的内容。
##### 代码清单 3.24:具有完整 HTML 结构的首页 RED
app/views/static_pages/home.html.erb
```
<!DOCTYPE html>
<html>
<head>
<title>Home | Ruby on Rails Tutorial Sample App</title>
</head>
<body>
<h1>Sample App</h1>
<p>
This is the home page for the
<a href="http://www.railstutorial.org/">Ruby on Rails Tutorial</a>
sample application.
</p>
</body>
</html>
```
修改之后,首页如[图 3.6](#fig-home-view-full-html) 所示。[[10](#fn-10)]
![home view full html](https://box.kancloud.cn/2016-05-11_5732bcf7efa31.png)图 3.6:添加标题后的首页
然后使用类似的方式修改“帮助”页面和“关于”页面,得到的代码如[代码清单 3.25](#listing-help-view-full-html) 和[代码清单 3.26](#listing-about-view-full-html) 所示。
##### 代码清单 3.25:具有完整 HTML 结构的“帮助”页面 RED
app/views/static_pages/help.html.erb
```
<!DOCTYPE html>
<html>
<head>
<title>Help | Ruby on Rails Tutorial Sample App</title>
</head>
<body>
<h1>Help</h1>
<p>
Get help on the Ruby on Rails Tutorial at the
<a href="http://www.railstutorial.org/#help">Rails Tutorial help
section</a>.
To get help on this sample app, see the
<a href="http://www.railstutorial.org/book"><em>Ruby on Rails
Tutorial</em> book</a>.
</p>
</body>
</html>
```
##### 代码清单 3.26:具有完整 HTML 结构的“关于”页面 GREEN
app/views/static_pages/about.html.erb
```
<!DOCTYPE html>
<html>
<head>
<title>About | Ruby on Rails Tutorial Sample App</title>
</head>
<body>
<h1>About</h1>
<p>
The <a href="http://www.railstutorial.org/"><em>Ruby on Rails
Tutorial</em></a> is a
<a href="http://www.railstutorial.org/book">book</a> and
<a href="http://screencasts.railstutorial.org/">screencast series</a>
to teach web development with
<a href="http://rubyonrails.org/">Ruby on Rails</a>.
This is the sample application for the tutorial.
</p>
</body>
</html>
```
现在,测试组件能通过了(**GREEN**):
##### 代码清单 3.27:**GREEN**
```
$ bundle exec rake test
3 tests, 6 assertions, 0 failures, 0 errors, 0 skips
```
## 3.4.3 布局和嵌入式 Ruby(重构)
到目前为止,本节已经做了很多事情,我们使用 Rails 控制器和动作生成了三个可用的页面,不过这些页面中的内容都是纯静态的 HTML,没有体现出 Rails 的强大之处。而且,代码中有着大量重复:
* 页面的标题几乎(但不完全)是一模一样的;
* 每个标题中都有“Ruby on Rails Tutorial Sample App”;
* 整个 HTML 结构在每个页面都重复地出现了。
重复的代码违反了很重要的“不要自我重复”(Don’t Repeat Yourself,简称 DRY)原则。本节要遵照 DRY 原则,去掉重复的代码。最后,我们要运行前一节编写的测试,确认显示的标题仍然正确。
不过,去除重复的第一步却是要增加一些代码,让页面的标题看起来是一样的。这样我们就能更容易地去掉重复的代码了。
在这个过程中,要在视图中使用嵌入式 Ruby(Embedded Ruby)。既然首页、“帮助”页面和“关于”页面的标题中有一个变动的部分,那我们就使用 Rails 提供的一个特别的函数 `provide`,在每个页面中设定不同的标题。通过把 `home.html.erb` 视图中标题的“Home”换成[代码清单 3.28](#listing-home-view-erb-title) 所示的代码,我们可以看一下这个函数的作用。
##### 代码清单 3.28:标题中使用了嵌入式 Ruby 代码的首页视图 GREEN
app/views/static_pages/home.html.erb
```
<% provide(:title, "Home") %> <!DOCTYPE html>
<html>
<head>
<title><%= yield(:title) %> | Ruby on Rails Tutorial Sample App</title>
</head>
<body>
<h1>Sample App</h1>
<p>
This is the home page for the
<a href="http://www.railstutorial.org/">Ruby on Rails Tutorial</a>
sample application.
</p>
</body>
</html>
```
在这段代码中我们第一次使用了嵌入式 Ruby,或者简称 ERb。(现在你应该知道为什么 HTML 视图文件的扩展名是 `.html.erb` 了。)ERb 是为网页添加动态内容主要使用的模板系统。[[11](#fn-11)]下面的代码
```
<% provide(:title, 'Home') %>
```
通过 `<% … %>` 调用 Rails 中的 `provide` 函数,把字符串 `"Home"` 赋给 `:title`。[[12](#fn-12)]然后,在标题中,我们使用类似的符号 `<%= … %>`,通过 Ruby 的 `yield` 函数把标题插入模板中:[[13](#fn-13)]
```
<title><%= yield(:title) %> | Ruby on Rails Tutorial Sample App</title>
```
(这两种嵌入 Ruby 代码的方式区别在于,`<% … %>` 只**执行**其中的代码;`<%= … %>` 也会执行其中的代码,而且会把执行的结果**插入**模板中。)最终得到的页面和以前一样,不过,现在标题中变动的部分通过 ERb 动态生成。
我们可以运行前一节编写的测试确认一下——测试还能通过(**GREEN**):
##### 代码清单 3.29:**GREEN**
```
$ bundle exec rake test
3 tests, 6 assertions, 0 failures, 0 errors, 0 skips
```
然后,按照相同的方式修改“帮助”([代码清单 3.30](#listing-help-view-erb-title))和“关于”页面([代码清单 3.31](#listing-about-view-erb-title))。
##### 代码清单 3.30:标题中使用了嵌入式 Ruby 代码的“帮助”页面视图 GREEN
app/views/static_pages/help.html.erb
```
<% provide(:title, "Help") %> <!DOCTYPE html>
<html>
<head>
<title><%= yield(:title) %> | Ruby on Rails Tutorial Sample App</title>
</head>
<body>
<h1>Help</h1>
<p>
Get help on the Ruby on Rails Tutorial at the
<a href="http://www.railstutorial.org/#help">Rails Tutorial help
section</a>.
To get help on this sample app, see the
<a href="http://www.railstutorial.org/book"><em>Ruby on Rails
Tutorial</em> book</a>.
</p>
</body>
</html>
```
##### 代码清单 3.31:标题中使用了嵌入式 Ruby 代码的“关于”页面视图 GREEN
app/views/static_pages/about.html.erb
```
<% provide(:title, "About") %> <!DOCTYPE html>
<html>
<head>
<title><%= yield(:title) %> | Ruby on Rails Tutorial Sample App</title>
</head>
<body>
<h1>About</h1>
<p>
The <a href="http://www.railstutorial.org/"><em>Ruby on Rails
Tutorial</em></a> is a
<a href="http://www.railstutorial.org/book">book</a> and
<a href="http://screencasts.railstutorial.org/">screencast series</a>
to teach web development with
<a href="http://rubyonrails.org/">Ruby on Rails</a>.
This is the sample application for the tutorial.
</p>
</body>
</html>
```
至此,我们把页面标题中的变动部分都换成了 ERb。现在,各个页面的内容类似下面这样:
```
<% provide(:title, "The Title") %>
<!DOCTYPE html>
<html>
<head>
<title><%= yield(:title) %> | Ruby on Rails Tutorial Sample App</title>
</head>
<body>
Contents
</body>
</html>
```
也就是说,所有的页面结构都是一致的,包括 `title` 标签中的内容,只有 `body` 标签中的内容有些差别。
为了提取出共用的结构,Rails 提供了一个特别的布局文件,名为 `application.html.erb`。我们在 [3.4 节](#slightly-dynamic-pages)重命名了这个文件,现在改回来:
```
$ mv layout_file app/views/layouts/application.html.erb
```
若想使用这个布局,我们要把默认的标题换成前面几段代码中使用的嵌入式 Ruby:
```
<title><%= yield(:title) %> | Ruby on Rails Tutorial Sample App</title>
```
修改后得到的布局文件如[代码清单 3.32](#listing-application-layout) 所示。
##### 代码清单 3.32:这个演示应用的网站布局 GREEN
app/views/layouts/application.html.erb
```
<!DOCTYPE html>
<html>
<head>
<title><%= yield(:title) %> | Ruby on Rails Tutorial Sample App</title>
<%= stylesheet_link_tag 'application', media: 'all',
'data-turbolinks-track' => true %>
<%= javascript_include_tag 'application', 'data-turbolinks-track' => true %>
<%= csrf_meta_tags %>
</head>
<body>
<%= yield %>
</body>
</html>
```
注意,其中有一行比较特殊:
```
<%= yield %>
```
这行代码的作用是,把每个页面的内容插入布局中。没必要了解它的具体实现过程,我们只需知道,在布局中使用这行代码后,访问 /static_pages/home 时会把 `home.html.erb` 中的内容转换成 HTML,然后插入 `<%= yield %>` 所在的位置。
还要注意,默认的 Rails 布局文件中还有下面这几行代码:
```
<%= stylesheet_link_tag ... %>
<%= javascript_include_tag "application", ... %>
<%= csrf_meta_tags %>
```
这几行代码的作用是,引入应用的样式表和 JavaScript 文件(Asset Pipeline 的一部分,[5.2.1 节](chapter5.html#the-asset-pipeline)会介绍);Rails 中的 `csrf_meta_tags` 方法,作用是避免“跨站请求伪造”(Cross-Site Request Forgery,简称 CSRF,一种恶意网络攻击)。
现在,[代码清单 3.28](#listing-home-view-erb-title)、[代码清单 3.30](#listing-help-view-erb-title) 和 [代码清单 3.31](#listing-about-view-erb-title) 的内容还是和布局文件中类似的 HTML 结构,所以我们要把完整的结构删除,只保留需要的内容。清理后的视图如[代码清单 3.33](#listing-home-view-interior)、[代码清单 3.34](#listing-help-view-interior) 和 [代码清单 3.35](#listing-about-view-interior) 所示。
##### 代码清单 3.33:去除完整的 HTML 结构后的首页 GREEN
app/views/static_pages/home.html.erb
```
<% provide(:title, "Home") %>
<h1>Sample App</h1>
<p>
This is the home page for the
<a href="http://www.railstutorial.org/">Ruby on Rails Tutorial</a>
sample application.
</p>
```
##### 代码清单 3.34:去除完整的 HTML 结构后的“帮助”页面 GREEN
app/views/static_pages/help.html.erb
```
<% provide(:title, "Help") %>
<h1>Help</h1>
<p>
Get help on the Ruby on Rails Tutorial at the
<a href="http://www.railstutorial.org/#help">Rails Tutorial help section</a>.
To get help on this sample app, see the
<a href="http://www.railstutorial.org/book"><em>Ruby on Rails Tutorial</em>
book</a>.
</p>
```
##### 代码清单 3.35:去除完整的 HTML 结构后的“关于”页面 GREEN
app/views/static_pages/about.html.erb
```
<% provide(:title, "About") %>
<h1>About</h1>
<p>
The <a href="http://www.railstutorial.org/"><em>Ruby on Rails
Tutorial</em></a> is a
<a href="http://www.railstutorial.org/book">book</a> and
<a href="http://screencasts.railstutorial.org/">screencast series</a>
to teach web development with
<a href="http://rubyonrails.org/">Ruby on Rails</a>.
This is the sample application for the tutorial.
</p>
```
修改这几个视图后,首页、“帮助”页面和“关于”页面显示的内容还和之前一样,但是没有多少重复内容了。
经验告诉我们,即便是十分简单的重构,也容易出错,所以才要认真编写测试组件。有了测试,我们就无需手动检查每个页面,看有没有错误。初期阶段手动检查还不算难,但是当应用不断变大之后,情况就不同了。我们只需验证测试组件是否还能通过即可:
##### 代码清单 3.36:**GREEN**
```
$ bundle exec rake test
3 tests, 6 assertions, 0 failures, 0 errors, 0 skips
```
测试不能证明代码完全正确,但至少能提高正确的可能性,而且还提供了安全防护措施,避免以后出现问题。
## 3.4.4 设置根路由
我们修改了网站中的页面,也顺利开始编写测试了,在继续之前,我们要设置应用的根路由。与 [1.3.4 节](chapter1.html#hello-world)和 [2.2.2 节](chapter2.html#mvc-in-action)的做法一样,我们要修改 `routes.rb` 文件,把根路径 `/` 指向我们选择的页面。这里我们要指向前面创建的首页。(我还建议把 [3.1 节](#sample-app-setup)添加的 `hello` 动作从应用的控制器中删除。)如[代码清单 3.37](#listing-home-root-route) 所示,我们要把自动生成的 `get` 规则([代码清单 3.5](#listing-pages-routes))改成:
```
root 'static_pages#home'
```
我们把 `static_pages/home` 改成 `static_pages#home`,确保通过 `GET` 请求访问 `/` 时,会交给静态页面路由器中的 `home` 动作处理。修改路由后,首页如[图 3.7](#fig-home-root-route) 所示。(注意,修改路由之后,/static_pages/home 就无法访问了。)
##### 代码清单 3.37:把根路由指向首页
config/routes.rb
```
Rails.application.routes.draw do
root 'static_pages#home' get 'static_pages/help'
get 'static_pages/about'
end
```
![home root route](https://box.kancloud.cn/2016-05-11_5732bcf810cf4.png)图 3.7:在根路由上显示的首页
- Ruby on Rails 教程
- 致中国读者
- 序
- 致谢
- 作者译者简介
- 版权和代码授权协议
- 第 1 章 从零开始,完成一次部署
- 1.1 简介
- 1.2 搭建环境
- 1.3 第一个应用
- 1.4 使用 Git 做版本控制
- 1.5 部署
- 1.6 小结
- 1.7 练习
- 第 2 章 玩具应用
- 2.1 规划应用
- 2.2 用户资源
- 2.3 微博资源
- 2.4 小结
- 2.5 练习
- 第 3 章 基本静态的页面
- 3.1 创建演示应用
- 3.2 静态页面
- 3.3 开始测试
- 3.4 有点动态内容的页面
- 3.5 小结
- 3.6 练习
- 3.7 高级测试技术
- 第 4 章 Rails 背后的 Ruby
- 4.1 导言
- 4.2 字符串和方法
- 4.3 其他数据类型
- 4.4 Ruby 类
- 4.5 小结
- 4.6 练习
- 第 5 章 完善布局
- 5.1 添加一些结构
- 5.2 Sass 和 Asset Pipeline
- 5.3 布局中的链接
- 5.4 用户注册:第一步
- 5.5 小结
- 5.6 练习
- 第 6 章 用户模型
- 6.1 用户模型
- 6.2 用户数据验证
- 6.3 添加安全密码
- 6.4 小结
- 6.5 练习
- 第 7 章 注册
- 7.1 显示用户的信息
- 7.2 注册表单
- 7.3 注册失败
- 7.4 注册成功
- 7.5 专业部署方案
- 7.6 小结
- 7.7 练习
- 第 8 章 登录和退出
- 8.1 会话
- 8.2 登录
- 8.3 退出
- 8.4 记住我
- 8.5 小结
- 8.6 练习
- 第 9 章 更新,显示和删除用户
- 9.1 更新用户
- 9.2 权限系统
- 9.3 列出所有用户
- 9.4 删除用户
- 9.5 小结
- 9.6 练习
- 第 10 章 账户激活和密码重设
- 10.1 账户激活
- 10.2 密码重设
- 10.3 在生产环境中发送邮件
- 10.4 小结
- 10.5 练习
- 10.6 证明超时失效的比较算式
- 第 11 章 用户的微博
- 11.1 微博模型
- 11.2 显示微博
- 11.3 微博相关的操作
- 11.4 微博中的图片
- 11.5 小结
- 11.6 练习
- 第 12 章 关注用户
- 12.1 “关系”模型
- 12.2 关注用户的网页界面
- 12.3 动态流
- 12.4 小结
- 12.5 练习