Ruby on Rails チュートリアルをやってみる:[第3章]静的なページの作成+α編

railstutorial.jp

前回marino.hatenadiary.jp


第3章

ここでは簡単なサンプルアプリケーションを開発して今後のチュートリアルを楽に理解できるようにしていくようです。
詳しく言うと静的なページを作成していくようです。自動化テストの雰囲気も掴んでいきましょう!

ここから難しくなるのか理解できない人はとばしてどんどん進んでくれとのこと。思い立ったときに戻ってこれば理解できているらしい・・・


ではプロジェクトをつくっていきます。

$ cd rails_projects
$ rails new sample_app --skip-test-unit
$ cd sample_app

ここでいきなり出てくる、--skip-test-unitというオプションはtestディレクトリを作成しないためのものです。別のフレームワークでテストを行うため、Railsのテストは不要だそうです。

Gemfileを変更していきます。今回、新たに2つのgemが出てきます。

/Gemfile

source 'https://rubygems.org'
ruby '2.0.0'
#ruby-gemset=railstutorial_rails_4_0

gem 'rails', '4.0.5'

group :development, :test do
  gem 'sqlite3', '1.3.8'
  gem 'rspec-rails', '2.13.1'
end

group :test do
  gem 'selenium-webdriver', '2.35.1'
  gem 'capybara', '2.1.0'
end

gem 'sass-rails', '4.0.5'
gem 'uglifier', '2.1.1'
gem 'coffee-rails', '4.0.1'
gem 'jquery-rails', '3.0.4'
gem 'turbolinks', '1.1.1'
gem 'jbuilder', '1.0.2'

group :doc do
  gem 'sdoc', '0.3.20', require: false
end

group :production do
  gem 'pg', '0.15.1'
  gem 'rails_12factor', '0.0.2'
end


gemを実際にインストール

$ bundle install --without production
$ bundle update
$ bundle install

このサンプルアプリケーションの重大なセキュリティ上の問題が発生する可能性を回避するために以下を記述します。

config/initializers/secret_token.rb

# Be sure to restart your server when you modify this file.

# Your secret key is used for verifying the integrity of signed cookies.
# If you change this key, all old signed cookies will become invalid!

# Make sure the secret is at least 30 characters and all random,
# no regular words or you'll be exposed to dictionary attacks.
# You can use `rake secret` to generate a secure secret key.

# Make sure your secret_key_base is kept private
# if you're sharing your code publicly.
require 'securerandom'

def secure_token
  token_file = Rails.root.join('.secret')
  if File.exist?(token_file)
    # Use the existing token.
    File.read(token_file).chomp
  else
    # Generate a new token and store it in token_file.
    token = SecureRandom.hex(64)
    File.write(token_file, token)
    token
  end
end

SampleApp::Application.config.secret_key_base = secure_token

/.gitignore

# Ignore bundler config.
/.bundle

# Ignore the default SQLite database.
/db/*.sqlite3
/db/*.sqlite3-journal

# Ignore all logfiles and tempfiles.
/log/*.log
/tmp

# Ignore other unneeded files.
doc/
*.swp
*~
.project
.DS_Store
.idea
.secret

つぎにテストのためにRailsの設定をを変更します。

$ rails generate rspec:install

ここまで終わったらGitリポジトリを初期化します。

$ git init
$ git add .
$ git commit -m "Initial commit"

最初に、アプリケーションのルートディレクトリに置かれているREADMEファイルを書き換えます。


/README.rdoc

# Ruby on Rails チュートリアル:サンプルアプリケーション

これは、以下のためのサンプルアプリケーションです。
[*Ruby on Rails Tutorial*](http://railstutorial.jp/)
by [Michael Hartl](http://www.michaelhartl.com/).

次に、拡張子を.mdに変更し、コミットします。

$ git mv README.rdoc README.md
$ git commit -am "Improve the README"

このチュートリアルは今後、このサンプルアプリケーションを使っていくそうです。GitHub上にリポジトリを作成し、プッシュしましょう。

$ git remote add origin https://github.com/<ユーザー名>/sample_app.git
$ git push -u origin master

ここまで完了したらいよいよサンプルアプリケーションの開発です。

静的ページ

まずはGitでトピックブランチを作ります。これはイメージとしてメインではない枝分かれのようなもので、今回のように新しい機能やページを作成するときに使います。

git checkout -b static-pages

HomeページとHelpページ、Aboutページで使用するアクションをgenerateというスクリプトを使い、作成してみます。まずは最初の2つのアクションをつくります。

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

先ほどと同じようにテストを自動生成しないようにしています。

ではアクセスしてみましょう。

$ rails server 

サーバーを起動して、http://localhost:3000/static_pages_homeにアクセスしてみます。

f:id:marinok:20150617122342p:plain

今回の場合は、homeアクションが空のため、アクセスしても単に対応するビューが表示されるだけです。
このページで表示されている(Find me in app/views/static_pages/home.html.erb
)場所にビューがあるようです。


ここで一旦StaticPagesコントローラファイルをGitリポジトリに追加しておきます。

$ git add .
$ git commit -m "Add a StaticPages controller"

最初のテスト

Railsチュートリアルではアプリケーションの振る舞いをテストしながら実装する手法を採用してます。この開発手法は、テスト駆動開発から派生した振舞駆動開発と知られてます。これから頻繁に使うツールは、結合テスト (integration test) と単体テスト (unit test) の2つだそうです。
この章では結合テストを使っていきます。結合テストはユーザーがアプリケーションを使う際の一連のアクションをシミュレーションします。

テスト駆動開発の定義とは、最初にテストを作成し、次にコードを作成することです。

この節では、RSpec gemによって提供されるrspecコマンドを使ってテストを実行します。

それではいくつかのコンテンツをHomeページに追加してみます。まず、静的なページに対する結合テストを生成します。

$ rails generate integration_test static_pages

これにより、spec/requestsディレクトリにstatic_pages_spec.rbが生成されます。
このファイルにコードを以下のように書いていきます。

spec/requests/static_pages_spec.rb

require 'spec_helper'

describe "Static pages" do

  describe "Home page" do

    it "should have the content 'Sample App'" do
      visit '/static_pages/home'
      expect(page).to have_content('Sample App')
    end
  end
end

テストが正常に実行されるようにするためには、以下の1行をspec_helper.rbに追加する必要があります。

# This file is copied to spec/ when you run 'rails generate rspec:install'
.
.
.
RSpec.configure do |config|
  .
  .
  .
  config.include Capybara::DSL #追記
end

テストではさまざまなオプションが使用できます。今回は、コマンドラインrspecコマンドを実行してみます。なお、bundle execをこのコマンドの前に置くことで、Gemfile内で定義された環境でRSpecが実行されるように指示できます。

$ bundle exec rspec spec/requests/static_pages_spec.rb

実行すると以下のように結果が帰ってくると思います。
内容は失敗したというものです。

Failures:

  1) Static pages Home page should have the content 'Sample App'

     Failure/Error: expect(page).to have_content('Sample App')

       expected #has_content?("Sample App") to return true, got false

     # ./spec/requests/static_pages_spec.rb:9:in `block (3 levels) in <top (required)>'

Finished in 0.95141 seconds

1 example, 1 failure

Failed examples:

rspec ./spec/requests/static_pages_spec.rb:7 # Static pages Home page should have the content 'Sample App'

Randomized with seed 6905

テストをパスするために、Homeページのビューを書き換えてみます。

app/views/static_pages/home.html.erb

<h1>Sample App</h1>
<p>
    This is the home page for the
  <a href="http://railstutorial.jp/">Ruby on Rails Tutorial</a>
    sample application.
</p>

先ほどはテストでアクセスした際に、正しいコンテンツ(Sample App)が表示されていないとエラーが出たため、トップの見出しをSample Appに変更しました。

もう一度テストを実行してみます。

$ bundle exec rspec spec/requests/static_pages_spec.rb

今度は赤色(エラー)が出ないはずです。


Helpページも同様にやっていきます。

spec/requests/static_pages_spec.rb

require 'spec_helper'

describe "Static pages" do

  describe "Home page" do

    it "should have the content 'Sample App'" do
      visit '/static_pages/home'
      expect(page).to have_content('Sample App')
    end
  end

  describe "Help page" do

    it "should have the content 'Help'" do
      visit '/static_pages/help'
      expect(page).to have_content('Help')
    end
  end
end

テストを実行。

$ bundle exec rspec spec/requests/static_pages_spec.rb

同じくエラーが出ます。ビューを直しましょう。

app/views/static_pages/help.html.erb

<h1>Help</h1>
<p>
  Get help on the Ruby on Rails Tutorial at the
  <a href="http://railstutorial.jp/help">Rails Tutorial help page</a>.
  To get help on this sample app, see the
  <a href="http://railstutorial.jp/book">Rails Tutorial book</a>.
</p>

もう一度テストを実行してみると、パスできるはずです。


では先ほどあえて先送りにしたAboutページテスト駆動開発の手法で新しく追加します。
まずはAboutページのテストをつくります。

spec/requests/static_pages_spec.rb

require 'spec_helper'

describe "Static pages" do

  describe "Home page" do

    it "should have the content 'Sample App'" do
      visit '/static_pages/home'
      expect(page).to have_content('Sample App')
    end
  end

  describe "Help page" do

    it "should have the content 'Help'" do
      visit '/static_pages/help'
      expect(page).to have_content('Help')
    end
  end

  describe "About page" do

    it "should have the content 'About Us'" do
      visit '/static_pages/about'
      expect(page).to have_content('About Us')
    end
  end
end

まずはテストを実行します。

$ bundle exec rspec spec/requests/static_pages_spec.rb

エラーが出るはずです。その中に以下の警告があるはずです。

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

このメッセージは、/static_pages/aboutというルートをroutesファイルに追加する必要があるということを示してます。
追加していきましょう。

config/routes.rb

SampleApp::Application.routes.draw do
  get "static_pages/home"
  get "static_pages/help"
  get "static_pages/about"
  .
  .
  .
end

再びテストを実行。
先ほどのエラーは消えているはずです。
次は以下のエラーを直していきます。

The action 'about' could not be found for StaticPagesController

これはaboutアクションが無いために出ているのでStaticPagesコントローラの中にaboutアクションを追加します。

app/controllers/static_pages_controller.rb

class StaticPagesController < ApplicationController

  def home
  end

  def help
  end

  def about
  end
end

再びテストを実行。
今度はテンプレートが見当たらないと表示されるはずです。
テンプレート、いわゆるビューを作成します。

具体的には、app/views/static_pagesディレクトリの中にabout.html.erbというファイルを作成し、以下の内容を書き込みます。

app/views/static_pages/about.html.erb

<h1>About Us</h1>
<p>
  The <a href="http://railstutorial.org/">Ruby on Rails Tutorial</a>
  is a project to make a book and screencasts to teach web development
  with <a href="http://rubyonrails.org/">Ruby on Rails</a>. This
  is the sample application for the tutorial.
</p>

これでテストを実行して緑色が出れば終わりになります。
実際にブラウザからもアクセスできるかなど確かめることもできます。

以上で静的ページの作成は終わりです。

少しだけ動的なページ

次は、ページの内容を反映したタイトルを持ち、ページごとに内容が変化する、少しだけ動的なページを作成してみます。

最初にタイトルのテストを作成し、次にタイトルを追加し、最後にレイアウトファイルを使ってリファクタリングと重複の排除を行います。

まずは自動生成されているレイアウトファイルを無効にするために適当にファイル名を変更しておきます。

$ mv app/views/layouts/application.html.erb foobar # 一時的な変更

(mvはUnixのコマンドです。Windowsでファイル名を変更するには、ファイルブラウザから行うか、renameコマンドを使ってください)

テストを作成していきますが、内容はHTMLのtitleタグの内容を確認するもので、have_titleメソッドを使います。
以下のようにspecファイルを書き足していきます。

spec/requests/static_pages_spec.rb

require 'spec_helper'

describe "Static pages" do

  describe "Home page" do

    it "should have the content 'Sample App'" do
      visit '/static_pages/home'
      expect(page).to have_content('Sample App')
    end

    it "should have the title 'Home'" do
      visit '/static_pages/home'
      expect(page).to have_title("Ruby on Rails Tutorial Sample App | Home")
    end
  end

  describe "Help page" do

    it "should have the content 'Help'" do
      visit '/static_pages/help'
      expect(page).to have_content('Help')
    end

    it "should have the title 'Help'" do
      visit '/static_pages/help'
      expect(page).to have_title("Ruby on Rails Tutorial Sample App | Help")
    end
  end

  describe "About page" do

    it "should have the content 'About Us'" do
      visit '/static_pages/about'
      expect(page).to have_content('About Us')
    end

    it "should have the title 'About Us'" do
      visit '/static_pages/about'
      expect(page).to have_title("Ruby on Rails Tutorial Sample App | About Us")
    end
  end
end

テストを実行し、赤色が出ればとりあえず大丈夫です。
エラーを消していきましょう。
それと同時にWebページを正しく表示させるためにHTMLをすべて追加していきます。

まずはHomeページのビューから。

app/views/static_pages/home.html.erb

<!DOCTYPE html>
<html>
  <head>
    <title>Ruby on Rails Tutorial Sample App | Home</title>
  </head>
  <body>
    <h1>Sample App</h1>
    <p>
    This is the home page for the
      <a href="http://railstutorial.jp/">Ruby on Rails Tutorial</a>
    sample application.
    </p>
  </body>
</html>

テストを実行するとエラーが1つ減っているのが分かります。同じようにHelpとAboutもパスしていきます。

app/views/static_pages/help.html.erb

<!DOCTYPE html>
<html>
  <head>
    <title>Ruby on Rails Tutorial Sample App | Help</title>
  </head>
  <body>
    <h1>Help</h1>
    <p>
  Get help on the Ruby on Rails Tutorial at the
      <a href="http://railstutorial.jp/help">Rails Tutorial help page</a>.
  To get help on this sample app, see the
      <a href="http://railstutorial.org/book">Rails Tutorial book</a>.
    </p>
  </body>
</html>

app/views/static_pages/about.html.erb

<!DOCTYPE html>
<html>
  <head>
    <title>Ruby on Rails Tutorial Sample App | About Us</title>
  </head>
  <body>
    <h1>About Us</h1>
    <p>
  The <a href="http://railstutorial.org/">Ruby on Rails Tutorial</a>
  is a project to make a book and screencasts 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>

これでテストは完了です。
しかし、ここまででコードが甚だしく重複しています。
重複を取り除くテクニックの1つとして、ビューで埋め込みRubyを使用します。

それではHomeのビューのタイトルに含まれるHomeという文字を置き換えて、動作を確認します。

app/views/static_pages/home.html.erb

<% provide(:title, 'Home') %>
<!DOCTYPE html>
<html>
  <head>
    <title>Ruby on Rails Tutorial Sample App | <%= yield(:title) %></title>
  </head>
  <body>
    <h1>Sample App</h1>
    <p>
    This is the home page for the
      <a href="http://railstutorial.jp/">Ruby on Rails Tutorial</a>
    sample application.
    </p>
  </body>
</html>

これはERbというRubyの埋め込みの最初の例です。ファイルの拡張子が.html.erbとなっている理由はこのためです。

書き換えましたが表示される内容は以前と同じです。テストを実行してみてもエラーが出ないはずです。

続いてHelpページとAboutページも同様に、

app/views/static_pages/help.html.erb

<% provide(:title, 'Help') %>
<!DOCTYPE html>
<html>
  <head>
    <title>Ruby on Rails Tutorial Sample App | <%= yield(:title) %></title>
  </head>
  <body>
    <h1>Help</h1>
    <p>
  Get help on the Ruby on Rails Tutorial at the
      <a href="http://railstutorial.jp/help">Rails Tutorial help page</a>.
  To get help on this sample app, see the
      <a href="http://railstutorial.jp/book">Rails Tutorial book</a>.
    </p>
  </body>
</html>

app/views/static_pages/about.html.erb

<% provide(:title, 'About Us') %>
<!DOCTYPE html>
<html>
  <head>
    <title>Ruby on Rails Tutorial Sample App | <%= yield(:title) %></title>
  </head>
  <body>
    <h1>About Us</h1>
    <p>
      The <a href="http://railstutorial.jp/">Ruby on Rails Tutorial</a>
  is a project to make a book and screencasts 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>

ここまで書き換えると、ビューのページは大体同じ形になっています。
Railsには、共通の構造をまとめるためのapplication.html.erbという特別なレイアウトファイルがあります。
最初に名前を変えて無効にしていたので元に戻します。

$ mv foobar app/views/layouts/application.html.erb


以下のようにレイアウトファイルを変更します。

app/views/layouts/application.html.erb

<!DOCTYPE html>
<html>
<head>
  <title>Ruby on Rails Tutorial Sample App | <%= yield(:title) %></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 %>」というコードです。
このコードは各ページの内容をレイアウト挿入するためのものです。簡単に説明するとHTMLの基本構造をこのレイアウトファイルに記述し、内容部分だけこの「<%= yield %>」
の部分に置き換えて各ページをつくるというものです。

3つのページを直していきます。

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://railstutorial.jp/">Ruby on Rails Tutorial</a>
    sample application.
</p>

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://railstutorial.jp/help">Rails Tutorial help page</a>.
  To get help on this sample app, see the
  <a href="http://railstutorial.jp/book">Rails Tutorial book</a>.
</p>

app/views/static_pages/about.html.erb

<% provide(:title, 'About Us') %>
<h1>About Us</h1>
<p>
  The <a href="http://railstutorial.jp/">Ruby on Rails Tutorial</a>
  is a project to make a book and screencasts to teach web development
  with <a href="http://rubyonrails.org/">Ruby on Rails</a>. This
  is the sample application for the tutorial.
</p>


以上でコードの重複を大きく削減できました。テスト結果もパスできているはずです。

この章でやることはここで終わりです。


最後に差分をコミットしてマスターブランチにマージしておきましょう。

$ git add .
$ git commit -m "Finish static pages"

次にmasterブランチに移動し、差分をマージ。

$ git checkout master
$ git merge static-pages

中継点まで達したら、コードをリモートリポジトリにアップロードしておきましょう。

$ git push


以上で3章終了です。
チュートリアルではここから毎回演習がついているようです。
より理解を深めるために挑戦してみるのも良いと思います。
でも、分からないようならとばして、最後まで読み終えてから挑戦するのも一つの手だと思います。