tyoshikawa1106のブログ

- Force.com Developer Blog -

Rails:静的なページの作成を試してみました - Part 1

Railsチュートリアルの第三章です。前回はデモアプリをつくって動かしてみただけですが、第三章ではテストなどより実践的な内容になるみたいです。


はじめにプロジェクトを作成します。

$ cd rails_projects
$ rails _4.0.5_ new sample_app --skip-test-unit
$ cd sample_app


ここで使ったrailsの--skip-test-unitというオプションは、Test::Unitフレームワークと関連しているtestディレクトリを作成しないようにするオプションです。第三章ではRspecを使ったテストを行うようになっていました。


サンプルアプリケーションで使う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


このサンプルアプリケーションはパブリックリポジトリ (public repository) として公開されるので、Railsでセッション変数の暗号化に使用するための、秘密トークン (secret token) を必ず更新することが重要です。トークンをハードコードすることなく動的に生成するします。

秘密トークンを動的に生成する。

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

f:id:tyoshikawa1106:20150714232255p:plain

必ず.gitignoreを使用するようにし、.secretキーをリポジトリで公開しないようにします。

.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


次に、Test::Unitの代わりにRSpecを使うように、Railsの設定を変更します。これを行うには、rails generate rspec:installを実行します。

$ rails generate rspec:install

f:id:tyoshikawa1106:20150714233106p:plain


Gitの初期化

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


READMEファイルを編集します。

$ subl README.rdoc

編集内容はこちら。

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

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


拡張子を .md に変更し、Markdownファイルとして認識できるようにします。その後、これらの変更をコミットします。

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

f:id:tyoshikawa1106:20150714233922p:plain


GitHubにリポジトリを作成してPushします。

$ git remote add origin git@github.com:tyoshikawa1106/rails-sample-app.git
$ git push -u origin master


これで準備OKです。
f:id:tyoshikawa1106:20150714234205p:plain


練習といしてHerokuにもデプロイします。

$ heroku create
$ git push heroku master
$ heroku run rake db:migrate

# もし Heroku のデプロイに失敗したときは、次のコマンドを試してみてください。
$ rake assets:precompile
$ git add .
$ git commit -m "Add precompiled assets for Heroku"
$ git push heroku master


これにより、リモート環境にバックアップを置くことができ、本番環境で発生するエラーをなるべく早期に発見することができます。なお、Herokuにデプロイするときにエラーが発生した場合は、以下のコマンドを実行して本番環境のログを取得してください。このログは、問題を特定するときに役立ちます。

$ heroku logs

f:id:tyoshikawa1106:20150715212024p:plain


ここまでの準備が完了したら、次はサンプルアプリケーションの開発です。

静的ページの作成

まずはGitを使ってトピックブランチを作ります。

$ git checkout -b static-pages
$ git branch

f:id:tyoshikawa1106:20150715213018p:plain


Railsにはgenerateというスクリプトがあり、このスクリプトにコントローラ名を入力するだけで、このスクリプトがコントローラを作成してくれます。これより、複数の静的なページを取り扱うStaticPagesコントローラを作成します。具体的には、HomeページとHelpページ、Aboutページで使用するアクションを作ってみます。generateスクリプトは、任意の数のアクションを引数に取ることができます。

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

f:id:tyoshikawa1106:20150715213504p:plain

今回はRSpecのテストを使わないため、--no-test-frameworkというオプションを付け加えることで、RSpecのテストを自動的に生成しないようにしています。


コントローラ名をキャメルケース (訳注: 単語の頭文字を大文字にしてつなぎ合わせた名前) で渡すと、StaticPagesコントローラは、スネークケース (訳注: 単語間にアンダースコアを加えて繋ぎ合わせた名前) のファイル static_pages_controller.rb を自動的に生成してくれます。


StaticPagesコントローラを生成すると、config/routes.rbファイルが自動的に更新されます。Railsは、この routesファイルに記述されている内容 (ルーティング) に従って、 複数のURLとWebページを対応付けます。


StaticPagesコントローラ内のhomeアクションとhelpアクションで使用するルーティング。

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


このルールは、/static_pages/homeというURLに対するリクエストを、StaticPagesコントローラのhomeアクションと結びつけています。具体的には、getと書くことで、GETリクエストに対して該当するアクションを結びつけています。

get "static_pages/home"

f:id:tyoshikawa1106:20150715214116p:plain


生成されたStaticPagesコントローラ。
f:id:tyoshikawa1106:20150715214403p:plain


Homeページを出力する、生成されたビュー
f:id:tyoshikawa1106:20150715214526p:plain


この後、HomeページとHelpページのコンテンツを少しだけカスタマイズします。また、About ページにもコンテンツを追加していきます。それが終わったら、ページごとに異なるタイトルを表示する、ほんの少しだけ動的なコンテンツを追加していていきます。

次に進む前に、StaticPagesコントローラファイルをGitリポジトリに追加します

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

f:id:tyoshikawa1106:20150715214744p:plain

最初のテスト

Railsチュートリアル では、一字一句間違えることなく最初から正確に実装するのではなく、アプリケーションの振る舞いをテストしながら実装する直観的な手法を採用しています。この開発手法は、テスト駆動開発 (Test-Driven Develpment, TDD) から派生した振舞駆動開発 (Behavior-Driven Development, BDD) として知られています。


本書でこれから頻繁に使うツールは、結合テスト (integration test) と単体テスト (unit test) の2つです。テスト駆動開発の定義とは、アプリケーションを開発するときに最初にテストを作成し、次にコードを作成することです。この開発手法に慣れるまでには多少時間がかかるかもしれませんが、一度慣れてしまえば大きなメリットを得られます。


ただし、テスト駆動開発がどんな仕事に対しても常に正しい手法であるとは限りません。このことは十分に理解しておいてください。「最初にテストを書くべきである」、「テストはひとつひとつの機能を完全にカバーするべきである」、「すべての箇所をテストすべきである」などのような教条的な主張を正当化できる理由はどこにもありません。

テスト駆動開発

テスト駆動開発で最初に書く、失敗するテストのことを、一般的なテストツールでは「赤色 (Red)」と表現します (失敗時に表示が赤くなるツールが多いため)。同様に、次に書く、テストにパスするコードのことを「緑色 (Green)」と表現します。最後に、必要に応じてコードをリファクタリング (例えば、動作を変えずにコードを改善したり、冗長なコードを削除したりすること) します。このサイクルのことを「Red/Green/Refactor」と呼びます。

それでは、テスト駆動開発でいくつかのコンテンツをHomeページに追加してみましょう。トップレベルの見出し (h1) に "Sample App" という語を追加する作業もこの中に含まれます。 まず、静的なページに対する結合テスト (request spec) を生成するところから始めましょう。

$ rails generate integration_test static_pages

f:id:tyoshikawa1106:20150715215530p:plain


Homeページの内容をテストするコード。
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

f:id:tyoshikawa1106:20150715215654p:plain


RSpecはRubyの柔軟性の高さを応用して、テスト用の独自言語 (Domain-Specific Language: DSL) を定義しています。RSpecを使うためにRSpec独自の文法を理解する必要はありません。RSpecやCapybaraは英語に近い形で読めるように設計されています。そのため、Railsチュートリアルで取り上げるテスト例を読み進めるだけで、英語圏の方ならRSpecの文法を楽に扱えるようになります。

テストのタイトル(任意で指定できます)
describe "Home page" do

RSpec はダブルクォート (") で囲まれた文字列を無視しますので、ここにも人間にとってわかりやすい説明文を書きましょう (訳注: 英語で書くことで、メソッドのitと整合性が取れます)。

Capybaraのvisit機能を使って、ブラウザでの/static_pages/homeURLへのアクセスをシミュレーション
expect(page).to have_content('Sample App')


テストが正常に実行されるようにするために、 spec_helper.rbを編集する必要があります
spec/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


上のコマンドを実行すると、「テストが失敗した」という結果が返ってきます。

f:id:tyoshikawa1106:20150715220541p:plain


テストにパスするために、自動生成されたHomeページのHTMLを編集します。
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>


Capybara DSLをRSpecヘルパーファイルに追加する。

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


この変更でテストが正常に通るようになります。
f:id:tyoshikawa1106:20150715221839p:plain


Helpページについても、Homeページの例を参考にして、同じようなテストとアプリケーションコードを使用できることが推測できます。最初に、文字列を ’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


これでテストを実行すると2つの内1つがエラーになります。
f:id:tyoshikawa1106:20150715223741p:plain


app/views/static_pages/help.html.erbを修正すると通るようになります。

$ bundle exec rspec spec/requests/static_pages_spec.rb

f:id:tyoshikawa1106:20150715223915p:plain

f:id:tyoshikawa1106:20150715223954p:plain


ここまででテスト駆動開発のシンプルなテスト例を確認できました。