3.4 有点动态内容的页面

最后更新于:2022-04-01 22:28:17

# 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 结构 ``` Greeting

Hello, world!

``` 这段代码的最顶部是“文档类型声明”(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 ``` Home | Ruby on Rails Tutorial Sample App

Sample App

This is the home page for the Ruby on Rails Tutorial sample application.

``` 修改之后,首页如[图 3.6](#fig-home-view-full-html) 所示。[[10](#fn-10)] ![home view full html](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/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 ``` Help | Ruby on Rails Tutorial Sample App

Help

Get help on the Ruby on Rails Tutorial at the Rails Tutorial help section. To get help on this sample app, see the Ruby on Rails Tutorial book.

``` ##### 代码清单 3.26:具有完整 HTML 结构的“关于”页面 GREEN app/views/static_pages/about.html.erb ``` About | Ruby on Rails Tutorial Sample App

About

The Ruby on Rails Tutorial is a book and screencast series to teach web development with Ruby on Rails. This is the sample application for the tutorial.

``` 现在,测试组件能通过了(**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") %> <%= yield(:title) %> | Ruby on Rails Tutorial Sample App

Sample App

This is the home page for the Ruby on Rails Tutorial sample application.

``` 在这段代码中我们第一次使用了嵌入式 Ruby,或者简称 ERb。(现在你应该知道为什么 HTML 视图文件的扩展名是 `.html.erb` 了。)ERb 是为网页添加动态内容主要使用的模板系统。[[11](#fn-11)]下面的代码 ``` <% provide(:title, 'Home') %> ``` 通过 `<% …​ %>` 调用 Rails 中的 `provide` 函数,把字符串 `"Home"` 赋给 `:title`。[[12](#fn-12)]然后,在标题中,我们使用类似的符号 `<%= …​ %>`,通过 Ruby 的 `yield` 函数把标题插入模板中:[[13](#fn-13)] ``` <%= yield(:title) %> | Ruby on Rails Tutorial Sample App ``` (这两种嵌入 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") %> <%= yield(:title) %> | Ruby on Rails Tutorial Sample App

Help

Get help on the Ruby on Rails Tutorial at the Rails Tutorial help section. To get help on this sample app, see the Ruby on Rails Tutorial book.

``` ##### 代码清单 3.31:标题中使用了嵌入式 Ruby 代码的“关于”页面视图 GREEN app/views/static_pages/about.html.erb ``` <% provide(:title, "About") %> <%= yield(:title) %> | Ruby on Rails Tutorial Sample App

About

The Ruby on Rails Tutorial is a book and screencast series to teach web development with Ruby on Rails. This is the sample application for the tutorial.

``` 至此,我们把页面标题中的变动部分都换成了 ERb。现在,各个页面的内容类似下面这样: ``` <% provide(:title, "The Title") %> <%= yield(:title) %> | Ruby on Rails Tutorial Sample App Contents ``` 也就是说,所有的页面结构都是一致的,包括 `title` 标签中的内容,只有 `body` 标签中的内容有些差别。 为了提取出共用的结构,Rails 提供了一个特别的布局文件,名为 `application.html.erb`。我们在 [3.4 节](#slightly-dynamic-pages)重命名了这个文件,现在改回来: ``` $ mv layout_file app/views/layouts/application.html.erb ``` 若想使用这个布局,我们要把默认的标题换成前面几段代码中使用的嵌入式 Ruby: ``` <%= yield(:title) %> | Ruby on Rails Tutorial Sample App ``` 修改后得到的布局文件如[代码清单 3.32](#listing-application-layout) 所示。 ##### 代码清单 3.32:这个演示应用的网站布局 GREEN app/views/layouts/application.html.erb ``` <%= yield(:title) %> | Ruby on Rails Tutorial Sample App <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track' => true %> <%= javascript_include_tag 'application', 'data-turbolinks-track' => true %> <%= csrf_meta_tags %> <%= yield %> ``` 注意,其中有一行比较特殊: ``` <%= 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") %>

Sample App

This is the home page for the Ruby on Rails Tutorial sample application.

``` ##### 代码清单 3.34:去除完整的 HTML 结构后的“帮助”页面 GREEN app/views/static_pages/help.html.erb ``` <% provide(:title, "Help") %>

Help

Get help on the Ruby on Rails Tutorial at the Rails Tutorial help section. To get help on this sample app, see the Ruby on Rails Tutorial book.

``` ##### 代码清单 3.35:去除完整的 HTML 结构后的“关于”页面 GREEN app/views/static_pages/about.html.erb ``` <% provide(:title, "About") %>

About

The Ruby on Rails Tutorial is a book and screencast series to teach web development with Ruby on Rails. This is the sample application for the tutorial.

``` 修改这几个视图后,首页、“帮助”页面和“关于”页面显示的内容还和之前一样,但是没有多少重复内容了。 经验告诉我们,即便是十分简单的重构,也容易出错,所以才要认真编写测试组件。有了测试,我们就无需手动检查每个页面,看有没有错误。初期阶段手动检查还不算难,但是当应用不断变大之后,情况就不同了。我们只需验证测试组件是否还能通过即可: ##### 代码清单 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://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-05-11_5732bcf810cf4.png)图 3.7:在根路由上显示的首页
';