`
tntyao
  • 浏览: 10046 次
  • 来自: ...
社区版块
存档分类
最新评论

项目的静态页面

    博客分类:
  • ruby
 
阅读更多
据说这是欧莱礼下一版的封面。。。
创建静态页面

静态页面,究竟要干啥的?我们的页面总需要添加一些,这网站谁做的阿 (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 带有 home、help 动作,以及相对应的路由、视图,一个 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。

<h1>StaticPages#home</h1>
<p>Find me in app/views/static_pages/home.html.erb</p>

<h1>StaticPages#help</h1>
<p>Find me in app/views/static_pages/help.html.erb</p>

而 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 ,并添加如下内容:

<h1>新微博</h1>
<p>
  神秘的X项目<a href="http://ruby-china.org">新微博</a> Rails 示例教学
</p>

再运行一次测试:

bin/rspec spec/requests/static_pages_spec.rb

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

first-pass

接下来让我们加入帮助信息的页面,一样先写个测试,在 static_pages_spec.rb 的 describe "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

<h1>帮助</h1>
<p>
  更多相关 Ruby on Rails 帮助,请莅临
  <a href="http://ruby-china.org">Ruby-china</a>.
  本示例教程教学文章:
  <a href="http://ruby-china.org/topics/3239">1000 小时学会 Rails 系列</a>.
</p>

呵呵,有了测试以后就像是傻瓜开发一样,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 ,并填入:

<h1>关于新微博</h1>
<p>
    新微博是使用 <a href="http://rubyonrails.org">Ruby on Rails</a> 技术所开发的微博,网络新纪元,微博新时代,你今天微勃了吗?
</p>

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>
<html>
<head>
  <title>XWeibo</title>
  <%= stylesheet_link_tag    "application", :media => "all" %>
  <%= javascript_include_tag "application" %>
  <%= csrf_meta_tags %>
</head>
<body>

<%= yield %>

</body>
</html>

这个文件就是咱 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>
<html>
  <head>
    <title>Home | X Weibo</title>
  </head>
  <body>
        <h1>新微博</h1>
        <p>
          神秘的X项目<a href="http://ruby-china.org">新微博</a> Rails 示例教学
        </p>
  </body>
</html>

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

<!DOCTYPE html>
<html>
  <head>
    <title>Help | X Weibo</title>
  </head>
  <body>
        <h1>帮助</h1>
        <p>
          更多相关 Ruby on Rails 帮助,请莅临
          <a href="http://ruby-china.org">Ruby-china</a>.
          本示例教程教学文章:
          <a href="http://ruby-china.org/topics/3239">1000 小时学会 Rails 系列</a>.
        </p>
  </body>
</html>

app/views/static_pages/about.html.erb

<!DOCTYPE html>
<html>
  <head>
    <title>About | X Weibo</title>
  </head>
  <body>
        <h1>关于新微博</h1>
        <p>
            新微博是使用 <a href="http://rubyonrails.org">Ruby on Rails</a> 技术所开发的微博,网络新纪元,微博新时代,你今天微勃了吗?
        </p>
  </body>
</html>

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

title-all-pass

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

有一个 Rails 提供的函数叫做 provide,怎么用呢:
app/views/static_pages/home.html.erb

<% provide(:title, 'Home') %>
<!DOCTYPE html>
<html>
  <head>
    <title><%= yield(:title) %> | X Weibo</title>
  </head>
  <body>
        <h1>新微博</h1>
        <p>
          神秘的X项目<a href="http://ruby-china.org">新微博</a> Rails 示例教学
        </p>
  </body>
</html>

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

这样到这行的时候:

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

就会替换成

<title>Home | X Weibo</title>

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

运行测试看看对不对:

bin/rspec spec/requests/static_pages_spec.rb

绿色!正是我们想要的结果,让我们再次依样画葫芦改动 About, Help 页面:
app/views/static_pages/about.html.erb

<% provide(:title, 'About') %>
<!DOCTYPE html>
<html>
  <head>
    <title><%= yield(:title) %> | X Weibo</title>
  </head>
  <body>
        <h1>关于新微博</h1>
        <p>
            新微博是使用 <a href="http://rubyonrails.org">Ruby on Rails</a> 技术所开发的微博,网络新纪元,微博新时代,你今天微勃了吗?
        </p>
  </body>
</html>

app/views/static_pages/help.html.erb

<% provide(:title, 'Help') %>
<!DOCTYPE html>
<html>
  <head>
    <title><%= yield(:title) %> | X Weibo</title>
  </head>
  <body>
        <h1>帮助</h1>
        <p>
          更多相关 Ruby on Rails 帮助,请莅临
          <a href="http://ruby-china.org">Ruby-china</a>.
          本示例教程教学文章:
          <a href="http://ruby-china.org/topics/3239">1000 小时学会 Rails 系列</a>.
        </p>
  </body>
</html>

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

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>
<html>
<head>
  <title><%= yield(:title) %> | X Weibo</title>
  <%= stylesheet_link_tag    "application", :media => "all" %>
  <%= javascript_include_tag "application" %>
  <%= csrf_meta_tags %>
</head>
<body>

<%= yield %>

</body>
</html>

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

接下来把之前重复的部份拿掉,拿掉后的 Home, Help, About 页面:
app/views/static_pages/home.html.erb

<% provide(:title, 'Home') %>
<h1>新微博</h1>
<p>
  神秘的X项目<a href="http://ruby-china.org">新微博</a> Rails 示例教学
</p>

app/views/static_pages/help.html.erb

<% provide(:title, 'Help') %>
<h1>帮助</h1>
<p>
  更多相关 Ruby on Rails 帮助,请莅临
  <a href="http://ruby-china.org">Ruby-china</a>.
  本示例教程教学文章:
  <a href="http://ruby-china.org/topics/3239">1000 小时学会 Rails 系列</a>.
</p>

app/views/static_pages/about.html.erb

<% provide(:title, 'About') %>
<h1>关于新微博</h1>
<p>
    新微博是使用 <a href="http://rubyonrails.org">Ruby on Rails</a> 技术所开发的微博,网络新纪元,微博新时代,你今天微勃了吗?
</p>

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

......

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 个小时。。。

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

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

待续。。。
习题:

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

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

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

相关推荐

Global site tag (gtag.js) - Google Analytics