Rails 1000 个小时学会 Rails - 005 X 项目的静态页面

juanito · 发布于 2012年5月16日 · 最后由 juanito 回复于 2016年6月07日 · 8615 次阅读
1510

1000 个小时学会 Rails 系列

上一回: 004 神秘的 X 项目

首先这个神秘的五十一区,给我送上了一张海报,希望能让我在开发新微博的时候,

时刻的记著这微勃的精神:

cover

一张苍老师代言的 “深入浅出” Rails 海报。。。据说这是欧莱礼下一版的封面。。。

创建静态页面

静态页面,究竟要干啥的?我们的页面总需要添加一些,这网站谁做的阿 (about),这网站怎么用阿 (help),这网站怎么回首页阿 (home)。。。等等,这些比较不会变化的页面,吾人称之为静态页面。

首先就先来实现刚刚说的这些页面,让我们先来创一个平行时空吧,沈佳怡。。。:

git checkout -b static_pages

-b 是新建分支并切换到该分支。

再来让我们复习一下 MVC,当一个尊敬的用户,打开浏览器,浏览你的网站时,会发送请求给你,Rails 有一个 Rails 路由,看了看你要去哪,交给相对应的控制器的动作,控制器跟模型要资料(如果需要的话),模型去数据库取出来,丢回给控制器,控制器传给视图,视图返回 HTML 给控制器,控制器再丢回给尊敬的用户。

创建 HOME 与 HELP 页面

当你创建一个 Rails 应用时,路由已经是标配了,你所需要做的呢,是产生控制器、视图以及模型。好的,先让我们产生一个控制器,让它来帮我们处理这些静态页面的请求:

rails generate controller StaticPages home help --no-test-framework

就跟买咖啡一样,说你要什么、不要什么。。。我们产生了什么呢:

create app/controllers/static_pages_controller.rb route get "static_pages/help" route get "static_pages/home" invoke erb create app/views/static_pages create app/views/static_pages/home.html.erb create app/views/static_pages/help.html.erb invoke helper create app/helpers/static_pages_helper.rb invoke assets invoke coffee create app/assets/javascripts/static_pages.js.coffee invoke scss create app/assets/stylesheets/static_pages.css.scss

首先呢,产生了一个控制器,叫做 StaticPages 带有 homehelp 动作,以及相对应的路由、视图,一个 helper、一个 coffeescript 及 sass 文件,不要缺省的测试框架,换了谭浩强来输入上面那条命令也是一样的结果,嘿嘿。。。

谭浩强:中国 C 语言之父。

假如有一天,你因为不明原因,只能用右手打字,哎呀、、、手误了,该怎么办?放心,有 generate 也有相对的 destroy 可以让你还原产生的东东:

rails destroy controller StaticPages home help 模型比照办理,如 rails destroy model Foo

让我们打开一下 config/routes.rb 看看,到底路由是加没加(注释暂时忽略)?

XWeibo::Application.routes.draw do get "static_pages/home"

get "static_pages/help" end

阿哈,它替我们创好了当有一个要到 URI static_pages/home 这样的 GET 请求时,把它交给 StaticPages 控制器的 home 动作处理。

HTTP 动词复习

这是用户(通常是浏览器,如火狐、Safari)与服务器(Apache、Nginx)之间的用语,请注意,你的电脑可以同时扮演两个角色。他们之间的沟通用语就是 GET, POST, PUT, DELETE。

  • GET 请求: 读取 ,比如到某个网站的首页,要求页面
  • POST 请求: 创建 ,比如填入表单所发送的请求
  • PUT 请求: 更新 ,比如更新用户信息。
  • DELETE 请求: 摧毁 ,比如不喜欢这个帐号?删除它。

有的人会说 PUT 跟 POST,也可以花插儿著用,恩可以,HTTP 允许 POST 来做更新。但在 Rails 应用的上下文里,POST 通常是拿来创建新东西。

好了,让我们再来看看控制器里有些什么:

class StaticPagesController < ApplicationController def home end

def help end end

这里可以看到,定义了一个类别 StaticPagesController 继承 (<)自 ApplicationController,并带有两个方法,方法由 def 关键字定义,这其实隐含了很多 Rails 帮你写好的代码。

然而这两个动作怎么啥都没有呢?在 Ruby 里,这两个方法啥也不干。在 Rails 呢,即便是没写内容的方法 home,最基本也会去渲染一个视图 ( app/views/static_pages/home.html.erb )。

让我们打开服务器一探究竟,我们总是有著无止尽的好奇心:

rails server

打开浏览器,看看这两个页面

http://localhost:3000/static_pages/home.html

http://localhost:3000/static_pages/help.html

home-help

这两个页面实际上就是 Rails 替我们产生的,相当简单的 HTML,各有一个 h1 标题标签,以及一个段落标签 p

StaticPages#home

Find me in app/views/static_pages/home.html.erb

StaticPages#help

Find me in app/views/static_pages/help.html.erb

http://localhost:3000/ 是 Rails 标准的欢迎画面,相信你已经相当熟悉了。

很好、很好,到这里先存档一下吧、我得去上个厕所:

git add . git commit -m 'Add a StaticPages controller'

撰写第一个测试

厕所上回来啦。。。还买了罐啤酒,BDD (aka Beer-driven Development) 嘛。。。啤酒少不了的。

如同之前说过的,这个应用会是由 BDD 的方式来做开发。所以现在让我们来写测试吧,当然,不用先写测试也可以,要理解到,虽然写测试有很多好处,但是人是活的,码是死的,我管不住你。。。

之后的流程,我们会先写一个失败的测试,撰写代码使其通过,再进行重构。

测试、编码、修改、重构。

第一步让我们先来产生测试:

rails generate integration_test static_pages

可以看到 Rails 调用了 rspec 帮我们产生了一个测试文件:

invoke rspec create spec/requests/static_pages_spec.rb

让我们加入 Home 页面的测试代码,你会发现里面已经有代码了,使用如下代码替换:

spec/requests/static_pages_spec.rb

require 'spec_helper'

describe "Static pages" do

describe "Home page" do

it "should have the content '新微博'" do visit '/static_pages/home' page.should have_content('新微博') end end end

让我来解释一下这个测试,我们有一个测试静态页面的 describe 区块

describe "Static pages" do

首先我们测的是 Home 页面

describe "Home page" do

里面有一个测试:

it "should have the content '新微博'" do visit '/static_pages/home' page.should have_content('新微博') end

这个测试就跟英文一样,它说当我们来到 /static_pages/home 这个页面时,应该要看到新微博这个内容。这里的 visit 使用到了,我们之前安装的 capybara gem 的一个 visit 方法、page 也是由 capybara gem 提供的变量。

这些 describe, it 高抽象化的语言是 RSpec 写好,供你使用的 DSL 方法,搭配 capybara 使用来达到测试驱动开发的目的,一开始可能会觉得不太自在,但先不要纠结于是怎么实现的,用的熟练后回头看,自然而然就理解了。

OK,现在让我们来运行看看

bin/rspec spec/requests/static_pages_spec.rb

first-fail

阿哈,我们有了第一个失败测试,很好,这样让我们知道下一步该怎么走。

错误信息告诉我们 Home 页面缺少了 新微博 这个内容,让我们打开 app/views/static_pages/home.html.erb ,并添加如下内容:

新微博

神秘的X项目新微博 Rails 示例教学

再运行一次测试:

bin/rspec spec/requests/static_pages_spec.rb

这时候应该看到测试通过:

first-pass

接下来让我们加入帮助信息的页面,一样先写个测试,在 static_pages_spec.rbdescribe "Static pages" 区块中加入:

describe "Help page" do

it "should have the content '帮助'" do visit '/static_pages/help' page.should have_content('帮助') end end

呵呵,这个测试基本上跟刚刚一样,只不过改了几个小地方,下一步要做什么呢?让我们运行测试看看:

bin/rspec spec/requests/static_pages_spec.rb

help-fail

到这会儿你应该可以体会出,我很傻帽或者是测试带来的好处,首先我们构思我们的页面要实现什么功能,撰写一个测试,运行测试、编码。而每个测试之间的相似性很高,互相可以重用。

好的,现在让我们打开 app/views/static_pages/help.html.erb,添加:

app/views/static_pages/help.html.erb

帮助

更多相关 Ruby on Rails 帮助,请莅临 Ruby-china. 本示例教程教学文章: 1000 小时学会 Rails 系列.

呵呵,有了测试以后就像是傻瓜开发一样,so simple!

我们的 Home 与 Help 页面做好啦!

加入关于页面

现在让我们加入一下关于页面,哎呀,看起来我刚刚似乎产生控制器的时候,忘了加入 about 页面了:

rails generate controller StaticPages home help --no-test-framework

呵呵,其实我是故意的,我们自己走一遍这个流程就可以了解 rails 命令背后到底偷偷摸摸干了啥,一样我们先来写个测试:

describe "About page" do it "should have the content '关于新微博" do visit '/static_pages/about' page.should have_content('关于新微博') end end

这个测试跟刚刚两个基本一模一样,将这个测试加至 static_pages_spec.rb ,你懂得,运行:

about-fail-no-route

哦,这次的错误信息不太一样,报错的是没有相应的路由:

No route matches [GET] "/static_pages/about"

若是 Rails 自动帮我们产生的话,Rails 会自动替你加路由、加相应的控制器动作、对应的视图。

让我们现在把这个路由加上,打开 config/routes.rb ,添加:

get "static_pages/about"

若是使用了 Spork 进阶配置的朋友,可能得重启 DRb 服务器哦,亲!

再运行测试看看,相信聪明如你已经猜到我们还没实现控制器的 about 动作:

about-fail-no-action

打开 app/controller/static_pages 添加 about 动作:

def about

end

现在先留白就很够用了,我们已经有从 Rails 的 ApplicationController 继承来的一堆东西可用了,这算不算是某种程度上的靠爸族呢。。。

about-fail-no-view

添加缺少的视图,在 app/views/static_pages 目录下创建 about.html.erb ,并填入:

关于新微博

新微博是使用 Ruby on Rails 技术所开发的微博,网络新纪元,微博新时代,你今天微勃了吗?

OK! 现在运行测试:

bin/rspec spec/requests/static_pages_spec.rb

都变成绿油油的一片,没有红色可怕的 F 了!这也就是为什么这个流程叫做 Red-Green-Refactor

现在让我们来看看有哪儿可以重构的。。。

重构!将 DRY 进行到底。。。

恩,我们都不喜欢单调,尤其是姿势,点缀生活,网站当然也一样,你发现到我们的标题老是一样么?Home, Help, About 页面的标题都是一样的,这样有点闷...

让我们看看如何让不同的页面有不同的标题,我们想要的是

title-table

OK. 现在让我们看看如何实现这个功能,首先我们先写个测试,来免去手动刷新的开发范式 (paradigm)...

打开 spec/requests/static_pages_spec.rb

让我们添加一个新测试:

it "should have the title 'Home'" do visit '/static_pages/home' page.should have_selector('title', :text => "Home | X Weibo") end

这个测试该加在哪呢?这是一个关于 Home 页面的测试,合理地我们可以放在 describe "Home" 区块里。

我们首先访问首页 visit '/static_pages/home' ,页面应该有内容为 Home | X Weibo 的标题。

这个 have_selector 方法确认 title 标签的内容是不是 Home | X Weibo ...

这里 :text => "Home | X Weibo" 是 Ruby 的嘻哈哈希语法。

:text 是 Ruby 里的符号,作为哈希的键值使用。

另一件要提及的是代码的可读性。注意到:

page.should have_selector('title', :text => "Home | X Weibo")

我们将

:text => "Home | X Weibo"

移到第二行,增加可读性。适当的缩排是提高可读性的不二法门。Ruby 不在乎多出来的空白及新行,最好是保持 每行 < 72 个字符,否则有可能会被 Linus 喷的体无完肤,呵呵。

让我们也添加 Help, About 页面的测试,现在整个 static_pages_spec.rb 看起来:

spec/requests/static_pages_spec.rb

# encoding: UTF-8

require 'spec_helper'

describe "Static pages" do

describe "Home page" do

it "should have the h1 '新微博'" do visit '/static_pages/home' page.should have_selector('h1', :text => '新微博') end

it "should have the title 'Home'" do visit '/static_pages/home' page.should have_selector('title', :text => "Home | X Weibo") end end

describe "Help page" do

it "should have the h1 '帮助'" do visit '/static_pages/help' page.should have_selector('h1', :text => '帮助') end

it "should have the title 'Help'" do visit '/static_pages/help' page.should have_selector('title', :text => "Help | X Weibo") end end

describe "About page" do

it "should have the h1 '关于新微博'" do visit '/static_pages/about' page.should have_selector('h1', :text => '关于新微博') end

it "should have the title 'About'" do visit '/static_pages/about' page.should have_selector('title', :text => "About | X Weibo") end end

end

注意我把每个页面比较暧昧不明的 have_content 换成 have_selector

好了,让我们运行测试看看:

bin/rspec spec/requests/static_pages_spec.rb

会看到三个测试 Fail 了,有著相似的错误信息:

Failure/Error: page.should have_selector('title', expected css "title" with text "Home | X Weibo" to return something

阿哈,我们要对红通通失败的测试感到开心,因为我们知道错在那,可以改,不像谈恋爱那般。。。

在这之前,让我们回想一下之前,我们创建项目时输入的命令么:

rails new x_weibo --skip-test-unit --skip-bundle

这个命令也替我们产生了一个 app/views/layouts/application.html.erb layout 文件:

app/views/static_pages/application.html.erb

<!DOCTYPE html>

XWeibo <%= stylesheet_link_tag "application", :media => "all" %> <%= javascript_include_tag "application" %> <%= csrf_meta_tags %>

<%= yield %>

这个文件就是咱 Rails 应用的骨架了,注意到中间的 <%= yield %>,我们的 Home, Help, About 页面会在调用 yield 时产生出来,这是 Rails 能够摆脱 那些年我们一起在网页里塞 PHP 代码 的精妙之处。

让我们先暂时把这个文件更名一下,先让它不起作用

mv app/views/layouts/application.html.erb foo

Windows 的命令貌似是 rename,不那么 Geek 手动更名也成。

好了,现在你启动服务器 (rails s),打开每个页面看看:

http://localhost:3000/static_pages/home http://localhost:3000/static_pages/help http://localhost:3000/static_pages/about

可以看到本来一样的标题都不见了,检视原始码发现甚至 HTML, DOCTYPE 那些标签都没了,相信你已经知道是怎么回事儿了,呵呵。

现在让我们分别实现每个页面的标题,首先是 Home 页面,打开 app/views/static_pages/home.html.erb ,让我们加上需要的 HTML ,让它成为一个完整的网页,并把标题改为我们要的样子:

app/views/static_pages/home.html.erb

<!DOCTYPE html>

Home | X Weibo

新微博

神秘的X项目新微博 Rails 示例教学

现在运行测试会发现失败的测试少了一个,让我们依样画葫芦改动 About 以及 Help 页面:

app/views/static_pages/help.html.erb

<!DOCTYPE html>

Help | X Weibo

帮助

更多相关 Ruby on Rails 帮助,请莅临 Ruby-china. 本示例教程教学文章: 1000 小时学会 Rails 系列.

app/views/static_pages/about.html.erb

<!DOCTYPE html>

About | X Weibo

关于新微博

新微博是使用 Ruby on Rails 技术所开发的微博,网络新纪元,微博新时代,你今天微勃了吗?

这时你再运行测试,会发现是绿油油的通过:

title-all-pass

但是,这样子我们不是大大的重复了代码么?没错,现在让我们看看如何 DRY...(Don't Repeat Yourself)

有一个 Rails 提供的函数叫做 provide,怎么用呢:

app/views/static_pages/home.html.erb

<% provide(:title, 'Home') %> <!DOCTYPE html>

<%= yield(:title) %> | X Weibo

新微博

神秘的X项目新微博 Rails 示例教学

上面我们使用了 Rails 给的 provide 方法来把 :title 与传入的字串做关联。

这样到这行的时候:

<%= yield(:title) %> | X Weibo

就会替换成

Home | X Weibo

<%= ... %> 会将结果显示至页面、<% %> 不会。

运行测试看看对不对:

bin/rspec spec/requests/static_pages_spec.rb

绿色!正是我们想要的结果,让我们再次依样画葫芦改动 About, Help 页面:

app/views/static_pages/about.html.erb

<% provide(:title, 'About') %> <!DOCTYPE html>

<%= yield(:title) %> | X Weibo

关于新微博

新微博是使用 Ruby on Rails 技术所开发的微博,网络新纪元,微博新时代,你今天微勃了吗?

app/views/static_pages/help.html.erb

<% provide(:title, 'Help') %> <!DOCTYPE html>

<%= yield(:title) %> | X Weibo

帮助

更多相关 Ruby on Rails 帮助,请莅临 Ruby-china. 本示例教程教学文章: 1000 小时学会 Rails 系列.

运行测试看看是否有打错字:

bin/rspec spec/requests/static_pages.spec.rb

......

Finished in 0.50721 seconds 6 examples, 0 failures

OK! 没有打错字或是奇怪的错误。

但是我们还是大大的重复了代码,每一页我们都重复了一堆相同的 HTML 代码,这就是为什么我们有一个 application.html.erb,现在我们先把刚刚更名的名字换回来 application.html.erb,:

mv foo app/views/layouts/application.html.erb

并依照刚刚的逻辑把 application.html.erb 改为:

<!DOCTYPE html>

<%= yield(:title) %> | X Weibo <%= stylesheet_link_tag "application", :media => "all" %> <%= javascript_include_tag "application" %> <%= csrf_meta_tags %>

<%= yield %>

我们一样在 title 标签 yield 不同的标题出来。

接下来把之前重复的部份拿掉,拿掉后的 Home, Help, About 页面:

app/views/static_pages/home.html.erb

<% provide(:title, 'Home') %>

新微博

神秘的X项目新微博 Rails 示例教学

app/views/static_pages/help.html.erb

<% provide(:title, 'Help') %>

帮助

更多相关 Ruby on Rails 帮助,请莅临 Ruby-china. 本示例教程教学文章: 1000 小时学会 Rails 系列.

app/views/static_pages/about.html.erb

<% provide(:title, 'About') %>

关于新微博

新微博是使用 Ruby on Rails 技术所开发的微博,网络新纪元,微博新时代,你今天微勃了吗?

运行测试依旧是青山绿水一片:

......

Finished in 0.52661 seconds 6 examples, 0 failures

好了,这样子我们的静态页面就做好了,有三个页面:Home, Help, About、并有不同的标题。。。

先存个档,存档之前记得先运行所有的测试确保无误:

git add . git commit -m "Finish static pages" git checkout master git merge static_pages

喔耶!我们首先 commit, 切回主分支 (master),并把劳动成果都合并过来。

接下来 push 到 Github 上:

git push

这个示例项目可以在 Github 上找到:神秘的 X 项目 Github

演示页面:神秘的 X 项目 Heroku (拜托不要给太多人知道,流量暴了会收钱的)

存档完毕。

不过现在还是弱暴了,但是慢慢来,不著急,还有 920 个小时。。。

让我们继续看看如何实现........

等等,五十一区的暴乳摔角格斗开始了,我要去观赛啦,今天就到这儿啦。。。

待续。。。

习题:

  1. 依照刚刚的思路写一个 contact 页面,先写个测试、创建 contact.html.erb、根据测试的报错慢慢调试、并加入适当的 title: Contact | X Weibo

  2. 我们的 static_pages_spec.rb 有著重复的基本标题用例:| X Weibo,试著去查询 RSpec 的 let 方法,看要怎么样 DRY。

  3. (选择性)参考 004 文章,将你的应用布署至 Heroku,你可能得用 PostgreSQL 才行,并在回应晒出你的 URL。

习题解答下回给出。

共收到 23 条回复
96

good, thanks

744

Thanks! 前面的都看完了~很有趣,也很有收获~

96

看完喽,很好的文章。一边看一边写代码。对ROR有了些感性认识。就是github和heroku没配好呢!回头还得再搞搞!

96

很不错的教程 是仿照Ruby on Rails 3 Tutorial 写的吗?

96

继续翻看楼主之前的教程,真是太棒了!

475

很好很强大。rails程序员都很有才。。。期待楼主加油。你快红了。

96

@fengyuntian heroku 暂时不要管。。github 倒是要学。。实际上这就是 RoR 3 Tutorial 的内容(@karma ),没必要按部就班的。。

96

楼主,等待你的下文呢>

15
  • 很好
  • 配图的妹子更好!
2345

楼主等你更新啊!你快回来。。。

2909

辛苦了,感谢大力贡献!

3018

学习中

96

<% provide(:title, 'Home') %> 应该是 <%= provide(:title, 'Home') %>

207

#13楼 @woaigithub

为什么呢? 这又不用显示出来

96

需要显示在title中。 无论显示在哪里都需要=。

207

#15楼 @woaigithub 后面 <%= yield %> 的时候有 = 了,前面参数赋值的时候不需要 = 了吧

96

#16楼 @blacktulip 是这样的,不需要了,我验证了,是我记错了。谢谢!

96

其实,封面还可以再给力些。。。

96

为什么就木有更新了咧

4552

更新 有木有?

5608

没有了吗

9484

后面怎么就木有了,求给力呀

9800

braindamaged......这词是从中文引入的吗?

1510 juanito 1000 个小时学会 Rails - 004 神秘的 X 项目 中提及了此贴 6月07日 21:43
需要 登录 后方可回复, 如果你还没有账号请点击这里 注册