Ruby on Rails チュートリアル

実例を使ってRailsを学ぼう

Michael Hartl (マイケル・ハートル)

第2版 目次

  1. 第1章 ゼロからデプロイまで
    1. 1.1はじめに
      1. 1.1.1読者の皆さまへ
      2. 1.1.2 Railsとスケールについて
      3. 1.1.3この本における取り決め
    2. 1.2 さっそく動作させる
      1. 1.2.1開発環境
        1. IDE
        2. テキストエディタとコマンドライン
        3. ブラウザ
        4. 使用するツールについて
      2. 1.2.2Ruby、RubyGems、Rails、Git
        1. Railsインストーラ (Windows)
        2. Gitのインストール
        3. Rubyのインストール
        4. RubyGemsのインストール
        5. Railsのインストール
      3. 1.2.3最初のアプリケーション
      4. 1.2.4 Bundler
      5. 1.2.5 rails server
      6. 1.2.6Model-view-controller (MVC)
    3. 1.3Gitによるバージョン管理
      1. 1.3.1インストールとセットアップ
        1. 初めてのシステムセットアップ
        2. 初めてのリポジトリセットアップ
      2. 1.3.2追加とコミット
      3. 1.3.3Gitのメリット
      4. 1.3.4 GitHub
      5. 1.3.5ブランチ (branch)、変更 (edit)、 コミット (commit)、マージ (merge)
        1. ブランチ (branch)
        2. 変更 (edit)
        3. コミット (commit)
        4. マージ (merge)
        5. プッシュ (push)
    4. 1.4デプロイする
      1. 1.4.1 Herokuのセットアップ
      2. 1.4.2 Herokuにデプロイする (1)
      3. 1.4.3 Herokuにデプロイする (2)
      4. 1.4.4 Heroku コマンド
    5. 1.5 最後に
  2. 第2章デモアプリケーション
    1. 2.1 アプリの計画
      1. 2.1.1ユーザーのモデル設計
      2. 2.1.2マイクロポストのモデル設計
    2. 2.2Users リソース
      1. 2.2.1ユーザーページを探検する
      2. 2.2.2 MVCの挙動
      3. 2.2.3Users リソースの欠点
    3. 2.3Microposts リソース
      1. 2.3.1マイクロポストのページを探検する
      2. 2.3.2マイクロポストをマイクロにする
      3. 2.3.3ユーザーとマイクロポストをhas_manyで関連づける
      4. 2.3.4継承の階層
      5. 2.3.5デモアプリケーションのデプロイ
    4. 2.4最後に
  3. 第3章ほぼ静的なページの作成
    1. 3.1静的ページ
    2. 3.2最初のテスト
      1. 3.2.1テスト駆動開発
      2. 3.2.2ページの追加
        1. 赤 (Red)
        2. 緑 (Green)
        3. リファクタリング
    3. 3.3少しだけ動的なページ
      1. 3.3.1タイトル変更をテストする
      2. 3.3.2タイトルのテストをパスさせる
      3. 3.3.3埋め込みRuby
      4. 3.3.4レイアウトを使って重複を解消する
    4. 3.4最後に
    5. 3.5演習
    6. 3.6高度なセットアップ
      1. 3.6.1bundle execを追放する
        1. RVM Bundler の統合
        2. binstubsオプション
      2. 3.6.2Guardによるテストの自動化
      3. 3.6.3Spork を使ったテストの高速化
        1. GuardにSporkを導入する
      4. 3.6.4Sublime Text上でテストする
  4. 第4章 Rails風味のRuby
    1. 4.1動機
    2. 4.2文字列(string)とメソッド
      1. 4.2.1コメント
      2. 4.2.2文字列
        1. 出力
        2. シングルクォート内の文字列
      3. 4.2.3オブジェクトとメッセージ受け渡し
      4. 4.2.4メソッドの定義
      5. 4.2.5 title ヘルパー、再び
    3. 4.3他のデータ構造
      1. 4.3.1配列と範囲演算子
      2. 4.3.2ブロック
      3. 4.3.3ハッシュとシンボル
      4. 4.3.4 CSS、再び
    4. 4.4 Ruby におけるクラス
      1. 4.4.1コンストラクタ
      2. 4.4.2クラス継承
      3. 4.4.3組込みクラスの変更
      4. 4.4.4コントローラクラス
      5. 4.4.5ユーザークラス
    5. 4.5最後に
    6. 4.6演習
  5. 第5章レイアウトを作成する
    1. 5.1構造を追加する
      1. 5.1.1ナビゲーション
      2. 5.1.2BootstrapとカスタムCSS
      3. 5.1.3パーシャル (partial)
    2. 5.2SassとAsset Pipeline
      1. 5.2.1Asset Pipeline
        1. アセットディレクトリ
        2. マニフェストファイル
        3. プリプロセッサエンジン
        4. 本番環境での効率性
      2. 5.2.2素晴らしい構文を備えたスタイルシート
        1. ネスト
        2. 変数
    3. 5.3レイアウトのリンク
      1. 5.3.1 ルートのテスト
      2. 5.3.2 Railsのルート
      3. 5.3.3名前付きルート
      4. 5.3.4RSpecを洗練させる
    4. 5.4ユーザー登録: 最初のステップ
      1. 5.4.1Usersコントローラ
      2. 5.4.2 ユーザー登録用URL
    5. 5.5最後に
    6. 5.6演習
  6. 第6章ユーザーのモデルを作成する
    1. 6.1 Userモデル
      1. 6.1.1データベースの移行
      2. 6.1.2modelファイル
      3. 6.1.3ユーザーオブジェクトを作成する
      4. 6.1.4ユーザーオブジェクトを検索する
      5. 6.1.5ユーザーオブジェクトを更新する
    2. 6.2ユーザーを検証する
      1. 6.2.1最初のユーザーテスト
      2. 6.2.2プレゼンスを検証する
      3. 6.2.3長さを検証する
      4. 6.2.4フォーマットを検証する
      5. 6.2.5一意性を検証する
        1. 一意性の警告
    3. 6.3セキュアなパスワードを追加する
      1. 6.3.1暗号化されたパスワード
      2. 6.3.2パスワードと確認
      3. 6.3.3ユーザー認証
      4. 6.3.4ユーザーがセキュアなパスワードを持っている
      5. 6.3.5ユーザーを作成する
    4. 6.4最後に
    5. 6.5演習
  7. 第7章ユーザー登録
    1. 7.1ユーザーを表示する
      1. 7.1.1デバッグとRails環境
      2. 7.1.2ユーザーリソース
      3. 7.1.3ファクトリーを使用してユーザー表示ページをテストする
      4. 7.1.4gravatar画像とサイドバー
    2. 7.2ユーザー登録フォーム
      1. 7.2.1ユーザー登録のためのテスト
      2. 7.2.2form_forを使用する
      3. 7.2.3フォームHTML
    3. 7.3ユーザー登録失敗
      1. 7.3.1正しいフォーム
      2. 7.3.2 Strong Parameters
      3. 7.3.3ユーザー登録のエラーメッセージ
    4. 7.4ユーザー登録成功
      1. 7.4.1登録フォームの完成
      2. 7.4.2flash
      3. 7.4.3実際のユーザー登録
      4. 7.4.4 SSLを導入して本番環境をデプロイする
    5. 7.5最後に
    6. 7.6演習
  8. 第8章サインイン、サインアウト
    1. 8.1セッション、サインインの失敗
      1. 8.1.1Sessionコントローラ
      2. 8.1.2サインインをテストする
      3. 8.1.3サインインのフォーム
      4. 8.1.4確認フォームを送信する
      5. 8.1.5フラッシュメッセージを表示する
    2. 8.2サインイン成功
      1. 8.2.1[このアカウント設定を保存する]
      2. 8.2.2正しいsign_inメソッド
      3. 8.2.3現在のユーザー
      4. 8.2.4レイアウトリンクを変更する
      5. 8.2.5ユーザー登録と同時にサインインする
      6. 8.2.6サインアウトする
    3. 8.3Cucumberの紹介 (オプション)
      1. 8.3.1インストールと設定
      2. 8.3.2フィーチャーとステップ
      3. 8.3.3対比: RSpecのカスタムマッチャー
    4. 8.4最後に
    5. 8.5演習
  9. 第9章 ユーザーの更新・表示・削除
    1. 9.1ユーザーを更新する
      1. 9.1.1編集フォーム
      2. 9.1.2編集の失敗
      3. 9.1.3編集の成功
    2. 9.2認可
      1. 9.2.1ユーザーのサインインを要求する
      2. 9.2.2正しいユーザーを要求する
      3. 9.2.3フレンドリーフォワーディング
    3. 9.3すべてのユーザーを表示する
      1. 9.3.1ユーザーインデックス
      2. 9.3.2サンプルのユーザー
      3. 9.3.3ページネーション
      4. 9.3.4パーシャルのリファクタリング
    4. 9.4ユーザーを削除する
      1. 9.4.1管理ユーザー
        1. Strong Parameters、再び
      2. 9.4.2 destroyアクション
    5. 9.5最後に
    6. 9.6演習
  10. 第10章ユーザーのマイクロポスト
    1. 10.1Micropostモデル
      1. 10.1.1基本的なモデル
      2. 10.1.2最初の検証
      3. 10.1.3User/Micropostの関連付け
      4. 10.1.4マイクロポストを改良する
        1. デフォルトのスコープ
        2. Dependent: destroy
      5. 10.1.5コンテンツの検証
    2. 10.2マイクロポストを表示する
      1. 10.2.1ユーザー表示ページの拡張
      2. 10.2.2マイクロポストのサンプル
    3. 10.3マイクロポストを操作する
      1. 10.3.1アクセス制御
      2. 10.3.2マイクロポストを作成する
      3. 10.3.3フィードの原型
      4. 10.3.4マイクロポストを削除する
    4. 10.4最後に
    5. 10.5演習
  11. 第11章ユーザーをフォローする
    1. 11.1Relationshipモデル
      1. 11.1.1データモデルの問題 (および解決策)
      2. 11.1.2User/relationshipの関連付け
      3. 11.1.3検証
      4. 11.1.4フォローしているユーザー
      5. 11.1.5フォロワー
    2. 11.2フォローしているユーザー用のWebインターフェイス
      1. 11.2.1フォローしているユーザーのサンプルデータ
      2. 11.2.2統計とフォロー用フォーム
      3. 11.2.3「フォローしているユーザー」ページと「フォロワー」ページ
      4. 11.2.4[フォローする] ボタン (標準的な方法)
      5. 11.2.5[フォローする] ボタン (Ajax)
    3. 11.3ステータスフィード
      1. 11.3.1動機と計画
      2. 11.3.2フィードを初めて実装する
      3. 11.3.3サブセレクト
      4. 11.3.4新しいステータスフィード
    4. 11.4最後に
      1. 11.4.1サンプルアプリケーションの機能を拡張する
        1. 返信機能
        2. メッセージ機能
        3. フォロワーの通知
        4. パスワードリマインダー
        5. ユーザー登録の確認
        6. RSSフィード
        7. REST API
        8. 検索機能
      2. 11.4.2読み物ガイド
    5. 11.5演習

前書き

私が前にいた会社 (CD Baby) は、かなり早い段階で Ruby on Rails に一度乗り換えたのですが、残念ながらまた PHP に戻ってしまいました (詳細は私の名前を Google で検索してみてください)。そんな私ですが、Michael Hartl 氏の本を強く勧められたので、その本を使ってもう一度試してみた結果、今度は無事に Rails に乗り換えることができました。それがこの Ruby on Rails チュートリアルという本です。

私は多くの Rails 関連の本を参考にしてきましたが、真の決定版と呼べるものは本書をおいて他にありません。本書では、あらゆる手順が Rails 流で行われています。最初のうちは慣れるまでに時間がかかりましたが、この本を終えた今、ついにこれこそが自然な方式だと感じられるまでになりました。また、本書は Rails 関連の本の中で唯一、多くのプロが推奨するテスト駆動開発 (TDD: Test Driven Development) を、全編を通して実践しています。実例を使ってここまで分かりやすく解説された本は、本書が初めてでしょう。極めつけは、Git や GitHub、Heroku の実例に含めている点です。このような、実際の開発現場で使わているツールもチュートリアルに含まれているため、読者は、まるで実際のプロジェクトの開発プロセスを体験しているかのような感覚が得られるはずです。それでいて、それぞれの実例が独立したセクションになっているのではなく、そのどれもがチュートリアルの内容と見事に一体化しています。

本書は、筋道だった一本道の物語のようになっています。私自身、章の終わりにある練習問題もやりながら、この Rails チュートリアルを3日間かけて一気に読破しました*1。最初から最後まで、途中を飛ばさずに読んでください。それが、最も有益な本書の読み方です。

それでは、楽しんでお読み下さい!

デレックシバーズ (Derek Sivers) (sivers.org) CD Baby 創始者

  1. 3日間で読破するのは異常です! 実際には3日以上かけて読むのが一般的です。

謝辞

Ruby on Rails チュートリアルは、私の以前の著書「RailsSpace」と、その時の共著者の Aurelius Prochazka から多くのことを参考にさせてもらっています。Aure には、RailsSpace での協力と本書への支援も含め、感謝したいと思います。また、RailsSpaceRails チュートリアルの両方の編集を担当して頂いた Debra Williams Cauley 氏にも謝意を表したく思います。彼女が野球の試合に連れて行ってくれる限り、私は本を書き続けるでしょう。

私にインスピレーションと知識を与えてくれた Rubyist の方々にも感謝したいと思います: David Heinemeier Hansson、 Yehuda Katz、 Carl Lerche、 Jeremy Kemper、 Xavier Noria、 Ryan Bates、 Geoffrey Grosenbach、 Peter Cooper、 Matt Aimonetti、 Gregg Pollack、 Wayne E. Seguin、 Amy Hoy、 Dave Chelimsky、 Pat Maddox、 Tom Preston-Werner、 Chris Wanstrath、 Chad Fowler、 Josh Susser、 Obie Fernandez、 Ian McFarland、 Steven Bristol、 Pratik Naik、 Sarah Mei、 Sarah Allen、 Wolfram Arnold、 Alex Chaffee、 Giles Bowkett、 Evan Dorn、 Long Nguyen、 James Lindenbaum、 Adam Wiggins、 Tikhon Bernstam、 Ron Evans、 Wyatt Greene、 Miles Forrest、 Pivotal Labs の方々、 Heroku の方々、 thoughtbot の方々、 そして GitHub の方々、ありがとうございました。最後に、ここに書ききれないほど多くの読者からバグ報告や提案を頂きました。彼ら/彼女らのおかげで、本書を可能な限り良い本に仕上げることが出来ました。

著者

マイケルハートル (Michael Hartl) は、「Ruby on Rails チュートリアル」という、Ruby on Rails を使って初めて Web アプリケーションを開発する際に最もよく参考にされる本の著者です。以前は、(今ではすっかり古くなってしまいましたが)「RailsSpace」という本の執筆および開発に携わったり、また、 一時人気を博した Ruby on Rails ベースのソーシャルネットワーキングプラットフォーム「Insoshi」の開発にも携わっていました。なお、2011年には、Rails コミュニティへの高い貢献が認められて、Ruby Hero Award を受賞しました。ハーバード大学卒業後、カリフォルニア工科大学物理学博士号を取得し、起業プログラム Y Combinator の卒業生でもあります。

著作権とライセンス

Ruby on Rails チュートリアル: 実例を使って Rails を学ぼう. Copyright © 2013 by Michael Hartl.

Ruby on Rails チュートリアル内の全てのソースコードは、MIT ライセンスおよび Beerware ライセンスの元で提供されています。(原文: All source code in the Ruby on Rails Tutorial is available jointly under the MIT License and the Beerware License.)

(訳注: "All source code" とはRailsチュートリアルの題材であるSNSのソースコードを指します。Railsチュートリアルという教材は上記ライセンスの元で提供されていないのでご注意ください)

(The MIT License)

Copyright (c) 2013 Michael Hartl

Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without restriction,
including without limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of the Software,
and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:

The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR
A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING OUT OF
OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
/*
 * ----------------------------------------------------------------------------
 * "THE BEER-WARE LICENSE" (Revision 42):
 * Michael Hartl wrote this code. As long as you retain this notice you
 * can do whatever you want with this stuff. If we meet some day,
 * and you think this stuff is worth it, you can buy me a beer in return.
 * ----------------------------------------------------------------------------
 */

第3章ほぼ静的なページの作成

本章では、今後のチュートリアルを楽に理解できるように、簡単なサンプルアプリケーションを開発してみます。本書を通して開発するアプリケーションは、最終的にはユーザーやマイクロポスト、ログイン/ログアウトなどの認証機能を持ちますが、まずは簡単なトピックである「静的なページの作成」から始めます。非常にシンプルなページではありますが、静的なページを作成することは良い経験になり、また、多くの示唆も得られます。私達がこれから開発するアプリケーションにとって、最高のスタート地点といえるでしょう。

Rails はデータベースと連携して動的なWebサイトを開発するように設計されていますが、HTMLファイルだけで構成されている静的なページを作ることもできます。実際、静的なページをRailsで作ることのメリットもあります。たとえば、あとでほんの少し動的なコンテンツを追加することができます。本章では、このような静的なページの作成について学んでいきます。まずは、自動化テストの雰囲気を掴んでいきます。自動化テストは、私達のコードが正しく動いているという自信を与えてくれます。さらに、良いテストを書くことで、自信をもってリファクタリングを行うことができます。たとえば、フォームの振る舞いを変更せずに、フォーム内で使われている関数を書き換えたいときに便利です。

本章には多くのサンプルコードがあります。特に 3.23.3 で多くのコードを紹介していますが、Rubyが初めての方は、コードを隅々まで理解しなければならないのだろうかと心配する必要はありません。1.1.1 で紹介したように、まずはテストをコピー&ペーストしてみて、アプリケーションがうまく動くかどうか検証してみると良いでしょう。この時点では、テストがどのようにして動くかは気にしなくても大丈夫です。また、第4章ではRubyについて解説します。そこではRubyの書き方やコンセプトについて学びます。最後に、本書ではRSpecを使ったテストを繰り返し実施していきます。ですから、もし途中でよく分からないテストがあったとしても、読み飛ばして先に進むことをお勧めします。1、2章先を読み進めた後に読み返してみると、当初はよく分からなかったテストが、実はとてもシンプルであることを理解できるはずです (Code SchoolのRSpecコースの履修を検討してみるのもよいでしょう。このコースはRSpecに関する多くの疑問に答えてくれるという読者からの報告もあります)。

では、第2章でもやったように、最初にRailsプロジェクトを作りましょう。今回はsample_appというプロジェクトを作成します。

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

ここで使ったrails--skip-test-unitというオプションは、Test::Unitフレームワークと関連しているtestディレクトリを作成しないようにするオプションです。これは「テストを書かないから」という理由ではありません。そうではなく、3.2以降では、もう1つのテストフレームワークであるRSpecを使ってテストを書くからです。

次は、2.1と同じように、テキストエディタを使ってGemfileに必要なgemを書き足していきます。今回は、2つの新しいgemを使います。RSpecのためのgemと、RSpecのライブラリのためのgemです。これらのgemをGemfileに追加すると、リスト3.1のようになります (: もしサンプルアプリケーションの開発で必要になるgemをすべて知りたい場合は、リスト9.47を参照してください。これが最終的なGemfileになります)。

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

このGemfileでは、開発環境とテスト環境でrspec-railsを使うようにしています。このため、開発環境ではRSpec固有のジェネレーターにアクセスすることができます。同様に、テスト環境でもRSpecを使用してテストを実行できるようになります。Gemfileに記述したrspec-railsが依存関係を解決してくれるため、 個々の環境にRSpec自身を手動でインストールする必要がなくなり、自動的にインストールされるようになります。同様の理由で、Capybara gemもGemfileに記述しています。これは、英語に近い文法を使って、ユーザーとサンプルアプリケーションの対話的操作をシミュレーションできるGemです1。CapybaraはSeleniumなどのgemに依存しています。

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

Herokuは、開発環境と本番環境とで同じデータベースを使うことを推奨していますが、今回開発するサンプルアプリケーションでは、データベースが異なっていても特に問題はありません。また、SQLiteはPostgreSQLに比べて極めて簡単に セットアップできます。なお、ローカルの開発マシンへのPostgreSQLのインストールと構築については、3.5の演習課題に含めました。

Gemfileに新しく追加したgemを実際にインストールするには、bundle updatebundle installを実行します。

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

1.4.1第2章でも説明したように、--without productionオプションを追加することで、本番環境のgemのみをインストールしないようにすることができます。注: このオプションは “remembered option” と呼ばれるもので、このオプションを一度実行するとコマンドに保存され、今後Bundlerを実行するときにオプションを追加する必要がなくなります。このため、今後は単にbundle installを実行するだけで、自動的に本番環境用gemをスキップできるようになります2

このサンプルアプリケーションはパブリックリポジトリ (public repository) として公開されるので、Railsでセッション変数の暗号化に使用するための、いわゆる秘密トークン (secret token) を必ず更新することが重要です。リスト3.2のコードを使用して、トークンをハードコードすることなく動的に生成するようにしてください。(リスト3.2のコードはやや高度なもので、本書のこの段階では時期尚早ではありますが、重大なセキュリティ上の問題が発生する可能性を回避するために、あえてこの段階に含めておくのがよいと考えます)。この場合、必ずリスト1.7の改訂版.gitignoreを使用するようにし、.secretキーをリポジトリでうっかり公開してしまうことのないようにしてください。

リスト 3.2 秘密トークンを動的に生成する。
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

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

$ rails generate rspec:install

(JavaScriptランタイムがインストールされていないというエラーが表示された場合は、GitHubのexecjsページにあるインストール可能なランタイムの一覧から入手してください。個人的にはNode.jsをお勧めします。)

ここまで進めたら、後はGitリポジトリを初期化するだけです3

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

最初に、リスト3.3のように、アプリケーションのルートディレクトリに置かれているREADMEファイルをわかりやすく書き換えてみましょう。

リスト 3.3 サンプルアプリケーション向けに書き換えたREADMEファイル。
# 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"

1.3.5git commit -a -m "Message"というGitコマンドを実行したことを思い出してください。あのときは “すべてを変更” (-a) オプションとコミットメッセージを追加するオプション (-m) を使用しました。上で実行したコマンドで示したように、実はこれらの2つのオプションを1つにまとめてgit commit -am "Message"と実行することができます。

本書では今後、このサンプルアプリケーションを使っていくことになるので、GitHub上にリポジトリを作成し、プッシュしておくと良いでしょう。

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

ここまで作業を進めると、著者がGitHubにアップロードしたRailsチュートリアルのサンプルアプリケーションのようになります (ユーザー名はrailstutorialで、アプリケーション名はsample_app_rails_4と若干異なります)4

もちろん、お望みであれば、この時点で 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

なお、本書を進める間、アプリケーションを定期的にGitHubにプッシュしたり、Herokuにデプロイすることをお勧めします。

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

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

$ heroku logs

ここまでの準備が完了したら、いよいよサンプルアプリケーションの開発を始めましょう。

3.1静的ページ

この節では、後に動的なページを作成するための準備として、最初にRailsのアクションビューに静的なHTMLだけを含めたものを作成します5。Railsのアクションは、コントローラ (1.2.6で紹介した MVCの Cに該当) の中に置かれる機能で、共通の目的を持つ一連のアクションをコントローラ内にまとめることができます。コントローラについては第2章でも簡単に触れましたが、第6章で説明するREST アーキテクチャを読むと、より深く理解することができます。一言で言うと、コントローラとは (基本的に動的な) Webページの集合を束ねるコンテナのことです。

現在どのディレクトリで作業しているかがわからなくなった場合は、1.2.3 (図 1.2)を再度参照して、Rails のディレクトリ構造を確認してください。この節では、主にapp/controllersディレクトリやapp/viewsディレクトリ内で作業を進めます (なお 3.2 では、新しいディレクトリをさらに1つ追加します)。好みのテキストエディタまたはIDEを使用してサンプルアプリケーションを開いてください (コラム 3.1)。

さっそく静的なページを作ってみましょう。まずは、1.3.5で紹介したように、Gitを使ってトピックブランチを作ります。今回のように新しい機能やページを作成するときは、masterブランチではなくトピックブランチで作業するのがよいでしょう。Git でバージョン管理をしている場合は、次のコマンドでトピックブランチを作成してください。

$ git checkout -b static-pages

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

リスト 3.4 StaticPagesコントローラを生成する。
$ 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

今回はRSpecのテストを使わないため、--no-test-frameworkというオプションを付け加えることで、RSpecのテストを自動的に生成しないようにしています。代わりに、3.2からは手動でテストを作成します。また、リスト 3.4に示したように、今回は意図的にaboutアクションをコマンドライン引数から取り除きました。 これは、同じく3.2 から、テスト駆動開発 (Test-Driven Development: TDD) という開発手法を学ぶためです。

リスト 3.4では、コントローラ名をキャメルケース (訳注: 単語の頭文字を大文字にしてつなぎ合わせた名前) で渡していることに注目してください。こうすると、StaticPagesコントローラは、スネークケース (訳注: 単語間にアンダースコアを加えて繋ぎ合わせた名前) のファイル static_pages_controller.rb を自動的に生成します。ただし、上のような命名は単なる慣習に過ぎません。実際、コマンドライン上で以下のようなスネークケースのコントローラ名を入力しても、

$ rails generate controller static_pages ...

先ほどと同様にstatic_pages_controller.rbというコントローラが生成されます。これは、Rubyがクラス名にキャメルケースを使う慣習があり (詳細は4.4で説明します)、また、キャメルケースの名前を使うことが好まれているためです。これらの慣習に必ず従わなければいけないということではありません。(同様にRubyでは、ファイル名をスネークケースで記述する慣習があります。このため Railsのgenerateスクリプトでは、 underscoreメソッドを使ってキャメルケースをスネークケースに変換しています。)

ところで、自動生成に失敗するようなことがあれば、元に戻す処理を学ぶ良い機会になります。以下のコラム 3.2で元に戻す方法を紹介しています。

リスト 3.4のようにStaticPagesコントローラを生成すると、config/routes.rbファイルが自動的に更新されます。Railsは、この routesファイルに記述されている内容 (ルーティング) に従って、 複数のURLとWebページを対応付けます。ここで、初めてconfigディレクトリについて触れます。このディレクトリは図 3.1のような構成になっています。configディレクトリという名前のとおり、このディレクトリ内にあるファイルは、Railsがアプリケーションの設定を読み込む時に必要になります。

config_directory_rails_4
図3.1サンプルアプリケーションのconfigディレクトリの内容。(拡大)

先ほどhomeアクションとhelpアクションを生成したので、次のリスト 3.5 に示されるように、routesファイルにはそれぞれのアクションで使用されるルールが定義されています。

リスト3.5 StaticPagesコントローラ内のhomeアクションとhelpアクションで使用するルーティング。
config/routes.rb
SampleApp::Application.routes.draw do
  get "static_pages/home"
  get "static_pages/help"
  .
  .
  .
end

ここで以下のルールに注目してみましょう。

get "static_pages/home"

このルールは、/static_pages/homeというURLに対するリクエストを、StaticPagesコントローラのhomeアクションと結びつけています。具体的には、getと書くことで、GETリクエストに対して該当するアクションを結びつけています。なお、ここでいう GETリクエストとは、HTTP (HyperText Transfer Protocol) (コラム 3.3) が対応しているメソッドの1つです。今回の場合は、StaticPagesコントローラ内にhomeアクションを追加したので、/static_pages/homeにアクセスすることでページを取得 (GET) できるようになりました。/static_pages/homeにアクセスして結果を表示します (図3.2)。

raw_home_view_31
図3.2/static_pages/homeにアクセスした結果。(拡大)

このページがどのようにして表示されるのかを理解するために、まずはテキストエディタでStaticPagesコントローラを開いてみましょう。リスト3.6のような内容になっているはずです。ここで、第2章のUsersコントローラやMicropostsコントローラとは異なり、StaticPagesコントローラは一般的なRESTアクションに対応していないことに注意してください。これは、静的なページの集合に対しては、適切なアクションと言えます。言い換えると、RESTアーキテクチャは、あらゆる問題に対して最適な解決方法であるとは限らないということです。

リスト3.6 リスト3.4で生成されたStaticPagesコントローラ。
app/controllers/static_pages_controller.rb
class StaticPagesController < ApplicationController

  def home
  end

  def help
  end
end

リスト 3.6classというキーワードから、static_pages_controller.rbStaticPagesControllerというクラスを定義していることがわかります。クラスは、関数 (メソッドとも呼ばれます) をまとめるときに便利な手法です。今回の例では、defというキーワードを使って、homeアクションやhelpアクションを定義しています。また、山括弧<は、StaticPagesControllerApplicationControllerというRailsのクラスを継承していることを示しています。この後も説明しますが、今回作成したページには、Rails特有の機能が多数使用されています (具体的なクラスや継承については、4.4で詳しく説明します)。

今回のStaticPagesコントローラにあるメソッドは、以下のようにどちらも最初は空になっています。

def home
end

def help
end

純粋なRuby言語であれば、これらのメソッドは何も実行しません。しかし、Railsでは動作が異なります。StaticPagesControllerはRuby のクラスですが、ApplicationControllerクラスを継承しているため、StaticPagesControllerのメソッドはRails特有の振る舞いをします。具体的には、/static_pages/homeというURLにアクセスすると、RailsはStaticPagesコントローラを参照し、homeアクションに記述されているコードを実行します。その後、そのアクションに対応するビュー (1.2.6で説明したMVCのVに相当) を出力します。今回の場合、homeアクションが空になっているので、/static_pages/homeにアクセスしても、単に対応するビューが出力されるだけになります。では、ビューはどのように出力されるのでしょうか。また、どのビューが表示されるのでしょうか。

リスト3.4を注意深く読んでみると、アクションとビューの関係性について理解できるでしょう。homeアクションは、home.html.erbというビューに対応しています。.erbの詳細については3.3で説明しますが、ファイル名に.htmlが含まれていることから推測できるとおり、基本的にはHTMLと同じような構造になっています (リスト3.7)。

リスト3.7 Homeページを出力する、生成されたビュー
app/views/static_pages/home.html.erb
<h1>StaticPages#home</h1>
<p>Find me in app/views/static_pages/home.html.erb</p>

helpアクションに対応するビューも、上のコードと似ています (リスト3.8)。

リスト3.8 Helpページを出力する、生成されたビュー
app/views/static_pages/help.html.erb
<h1>StaticPages#help</h1>
<p>Find me in app/views/static_pages/help.html.erb</p>

どちらのビューも単なるプレースホルダになっています。トップレベルの見出しがh1タグの中にあり、関連するファイルへの絶対パスがpタグの中に書かれています。3.3からは、(ほんの少しだけ) 動的なコンテンツを追加しますが、ここで重要なのは「Railsのビューは静的なHTMLで構成されている」という点です。

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

次に進む前に、StaticPagesコントローラファイルをGitリポジトリに追加しておきましょう。

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

3.2最初のテスト

Railsチュートリアル では、一字一句間違えることなく最初から正確に実装するのではなく、アプリケーションの振る舞いをテストしながら実装する直観的な手法を採用しています。この開発手法は、テスト駆動開発 (Test-Driven Develpment, TDD) から派生した振舞駆動開発 (Behavior-Driven Development, BDD) として知られています。本書でこれから頻繁に使うツールは、結合テスト (integration test) 単体テスト (unit test) の2つです。結合テストについてはこの節から、単体テストについては第6章から解説していきます。結合テスト (RSpec では リクエストspec と呼んでいます) は、ユーザーがアプリケーションを使う際の一連のアクションをシミュレーションします。結合テストは、アプリケーションの各ページが正常に動作するかどうかをテストしてくれる強力なツールです。手動でブラウザを操作してテストする必要がなくなり、Capybaraを併用すれば自然言語 (英語) に近い文法でテストを記述する事もできます (Capybara以外にも、Cucumberという振舞駆動開発用ツールが有名です。詳細については8.3で紹介します)。

テスト駆動開発の定義とは、アプリケーションを開発するときに最初にテストを作成し、次にコードを作成することです。この開発手法に慣れるまでには多少時間がかかるかもしれませんが、一度慣れてしまえば大きなメリットを得られます。失敗するテストを最初に書き、テストにパスするコードを次に実装することで、しかるべき振る舞いがテストによって正しく検証されている、という自信が付きます。さらに、この「失敗-実装-成功」という開発サイクルは、「フロー体験」を誘発します。フローに入ることで、コーディングが楽しくなり、開発の生産性も向上します。また、テストはアプリケーションのコードに対してクライアントとして振る舞うので、ソフトウェア設計の改良につながることも多くなるでしょう。

ただし、テスト駆動開発がどんな仕事に対しても常に正しい手法であるとは限りません。このことは十分に理解しておいてください。「最初にテストを書くべきである」、「テストはひとつひとつの機能を完全にカバーするべきである」、「すべての箇所をテストすべきである」などのような教条的な主張を正当化できる理由はどこにもありません。たとえば、与えられた課題の解決法に今ひとつ確信が持てないときは、(テストを書かずに) まず試しにアプリケーションコードだけを書いてみて、どんな解決方法があるのか模索してみる方が良い結果を得られることもあります (エクストリーム・プログラミング (Extreme Programming) という開発手法では、この模索段階をスパイク (spike) と呼んでいます)。そして解決策が明確になった段階で、テスト駆動開発でコードを清書するという方法もありえます。

この節では、RSpec gemによって提供されるrspecコマンドを使ってテストを実行します。この節は素直なつくりになっていますが、物足りないと思う方もいるかもしれません。上級開発者であれば、先に3.6を読んでシステム設定を完了しておくことをお勧めします。

3.2.1テスト駆動開発

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

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

$ rails generate integration_test static_pages
      invoke  rspec
      create    spec/requests/static_pages_spec.rb

これにより、spec/requestsディレクトリにstatic_pages_spec.rbが生成されます。自動生成される他のコードと同様に、最初のコードは完全ではありません。そこで、テキストエディタでstatic_pages_spec.rb を開き、リスト 3.9のようにコードを書いてみましょう。

リスト3.9 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

リスト 3.9のコードはすべてRubyで書かれています。しかし、Rubyを勉強したことがある人にとっては見慣れない書き方に見えるかもしれません。これは、Rubyの柔軟性の高さを応用して、RSpecがテスト用の独自言語 (Domain-Specific Language: DSL) を定義しているからです。ここで重要なのは、RSpecを使うためにRSpec独自の文法を理解する必要はないということです。最初のうちは魔法のように見えるかもしれませんが、RSpecやCapybaraは英語に近い形で読めるように設計されています。したがって、本チュートリアルで取り上げるテスト例を読み進めるだけで、英語圏の方ならRSpecの文法を楽に扱えるようになります。

リスト3.9にはdescribeというブロックがあり、そのブロック内には it "…" 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

最初の行では、Homeページに対するテストであることを記述しています。これは単なる文字列であり、好きな文字列を使用できます。RSpecはこの文字列を解釈しないので、人間にとってわかりやすい説明をここに書くようにします。次の行では、「/static_pages/homeのHomeページにアクセスしたとき、“Sample App”という語が含まれていなければならない」と記述しています。最初の行と同様で、RSpec はダブルクォート (") で囲まれた文字列を無視しますので、ここにも人間にとってわかりやすい説明文を書きましょう (訳注: 英語で should have...と書くことで、メソッドのitと整合性が取れます)。その次の行について説明します。

visit '/static_pages/home'

上の行は、Capybaraのvisit機能を使って、ブラウザでの/static_pages/homeURLへのアクセスをシミュレーションします。

expect(page).to have_content('Sample App')

その次の行 (上のコード) では、これもCapybaraが提供するpage変数を使って、アクセスした結果のページに正しいコンテンツが表示されているかどうかをテストしています。

テストが正常に実行されるようにするために、リスト3.10に示した1行を spec_helper.rbに追加する必要があります (Railsチュートリアルの第3版を出すときには、新しいfeature specsの技法を使用してこの行の追加を不要にすることを計画しています)。

リスト3.10
Capybara DSLをRSpecヘルパーファイルに追加する。
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

テストではさまざまなオプションが使用できます。さらに高度で便利なツールもあり、詳細については3.6で説明します。今回は、コマンドラインでrspecコマンドを実行してみてましょう (なお、bundle execをこのコマンドの前に置くことで、Gemfile内で定義された環境でRSpecが実行されるように、明示的に指示することができます6)。

$ bundle exec rspec spec/requests/static_pages_spec.rb

上のコマンドを実行すると、「テストが失敗した」という結果が返ってきます。 結果の表示はシステムによって異なりますが、著者のシステムでは、図3.3のように失敗したテストが赤く表示されます7

red_failing_spec_4_0
図3.3赤く表示されている (失敗した) テスト。(拡大)

テストにパスするために、自動生成されたHomeページのHTMLをリスト3.11のように書き換えてみましょう。

リスト3.11 テストにパスする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>

トップレベルの見出し (<h1>) がSample Appに変更されたため、上のコードはテストにパスします。また、アンカータグ a を使って、指定したURLにジャンプする以下のリンクを追加しました (ちなみにアンカータグ内の “href” は “hypertext reference” と読みます)。

<a href="http://railstutorial.jp/">Ruby on Rails Tutorial</a>

結果を見るために、もう一度テストを実行してみましょう。

$ bundle exec rspec spec/requests/static_pages_spec.rb

著者のシステムでは、図3.4のように表示されました。

green_passing_spec_4_0
図3.4緑色で表示されている (成功した) テスト。(拡大)

Helpページについても、Homeページの例を参考にして、同じようなテストとアプリケーションコードを使用できることが推測できます。最初に、文字列を ’Help’ に書き換えたテストを追加してみましょう (リスト 3.12)。

リスト3.12 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

テストのうち、1つは失敗するはずです。(注: 執筆作業軽減のため、今後はRSpecの出力結果を掲載しません。画面表示の内容はシステムによって大きく異なるうえ、各段階での出力画面数を把握してメンテナンスし続けるのが極めて困難なためです。ご了承ください。)

リスト3.13に示したように、このアプリケーションコードはリスト3.11と同じようなコードになります。

リスト3.13 テストにパスする、Helpページ用のコード。
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>

これでテストにパスするはずです。

$ bundle exec rspec spec/requests/static_pages_spec.rb

3.2.2ページの追加

先ほどのテスト駆動開発のシンプルなテスト例を参考に、もう少し複雑なタスクを実行するページを新規追加してみましょう。具体的には、3.1であえて先送りにしたAboutページを新しく追加します。段階ごとにテストを作成してRSpecを実行することで、テスト駆動開発によってアプリケーション開発を進める方法を理解できるようになるでしょう。

赤 (Red)

赤色から緑色にするために、最初にAboutページ用の失敗するテストを書き、赤色にしましょう。 リスト3.12を参考にすることで、正しいテストを推測できるでしょう(リスト3.14)。

リスト3.14 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

緑 (Green)

3.1でも触れたように、Railsでは、アクションと、それに対応するページ名を持つビューを作成することで、静的なページを生成することができます。今回の場合、 Aboutページを使用できるようにするには、aboutアクションをStaticPagesコントローラの中に追加する必要があります。最初に失敗するテストを書き、次にそのテストにパスするように実装することで、正常に動作するAboutページを作成できたという実感を得ることができます。

先ほど実装したRSpecのテストを実行します。

$ bundle exec rspec spec/requests/static_pages_spec.rb

上を実行した出力結果の中に、以下のような警告が含まれているはずです。

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

このメッセージは、/static_pages/aboutというルートをroutesファイルに追加する必要があるということを示しています。リスト3.5のパターンに従って、routesファイルをリスト3.15のように変更することでこの問題は解決します。

リスト3.15 about用のルートを追加する。
config/routes.rb
SampleApp::Application.routes.draw do
  get "static_pages/home"
  get "static_pages/help"
  get "static_pages/about"
  .
  .
  .
end

再び以下を実行します。

$ bundle exec rspec spec/requests/static_pages_spec.rb

今度は以下のエラーメッセージが発生します。

The action 'about' could not be found for StaticPagesController

この問題を解決するために、リスト 3.6homeアクションや helpアクションのパターンに従って、StaticPagesコントローラの中にaboutアクションを追加します (リスト 3.16)。

リスト3.16 aboutアクションが追加されたStaticPagesコントローラ。
app/controllers/static_pages_controller.rb
class StaticPagesController < ApplicationController

  def home
  end

  def help
  end

  def about
  end
end

再び以下を実行します。

$ bundle exec rspec spec/requests/static_pages_spec.rb

今度は、ビューなどの "テンプレート" が見当たらないというエラーメッセージが表示されます。

  Missing template static_pages/about

これは、aboutビューを追加することで解決します。具体的には、app/views/static_pagesディレクトリの中にabout.html.erbというファイルを作成し、リスト 3.17の内容を書き込みます。

リスト3.17 Aboutページのコード
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>

今度は、RSpecを実行すると緑色になるはずです。

$ bundle exec rspec spec/requests/static_pages_spec.rb

もちろん、実際にブラウザを起動して、テストが正しく動いているかどうかを確かめることもできます (図3.5)。

about_us_2nd_edition
図3.5作成したAboutページ (/static_pages/about)。(拡大)

リファクタリング

テストが緑色になったので、安心してコードをリファクタリングできるようになりました。多くの場合、コードを書き進めるうちに肥大化したり繰り返しが増えたりして、いつしか「悪臭を放つ」醜悪なコードになりはてるものです。コンピュータはコードが醜くても気にしませんが、開発者にとってはそうはいきません。だからこそ、頻繁にリファクタリングを実施し、コードを清潔な状態に保ち続けることが重要です。この点において、良いテストコードがあるということは非常に貴重です。リファクタリングする際にバグが混入する可能性を劇的に小さくしてくれるからです。

サンプルアプリケーションはかなり小規模なので、今のところリファクタリングの余地はありません。しかしコードの「悪臭」はやがてあらゆる箇所からただよいはじめ、そのうちにリファクタリングが必要になることでしょう。実際、3.3.4で早くもリファクタリングに取りかかることになります。

3.3少しだけ動的なページ

以上で、静的ページのアクションとビューを作成しました。次は、ページの内容を反映したタイトルを持ち、ページごとに内容が変化する、少しだけ動的なページを作成してみましょう。 タイトルを変えるぐらいのことが本当に動的コンテンツと呼べるかどうかは議論の余地があると思いますが、いずれにしろこのページは、第7章で紹介する真に動的なコンテンツの基礎となります。

3.2のテスト駆動開発の説明を読んでいない場合は、リスト3.15リスト3.16リスト3.17のコードを使ってAboutページを必ず作成しておいてください。

3.3.1タイトル変更をテストする

ここでの目標は、Home、Help、Aboutページをそれぞれ編集し、ページごとに異なるタイトルが表示されるようにすることです。ここではビューの<title>タグの内容を変更します。titleタグは、ほとんどのブラウザでウィンドウの上に表示されます (Google Chromeは例外ですが)。titleタグは、検索エンジン最適化 (SEO) においても重要な役割を担っています。最初にタイトルのテストを作成し、次にタイトルを追加し、最後にレイアウトファイルを使ってリファクタリングと重複の排除を行います。

レイアウトファイルは、rails newコマンドを実行していれば既に作成されているはずです。レイアウトファイルの役割についてはこの後説明しますが、まずは作業開始前にレイアウトファイルのファイル名を変更しておきましょう。

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

(mvはUnixのコマンドです。Windowsでファイル名を変更するには、ファイルブラウザから行うか、renameコマンドを使ってください)。実際のアプリケーション開発時には、上のような操作を行うことはおそらくないでしょう。ここでは、レイアウトファイルの役割をよりわかりやすく説明するために、最初にレイアウトファイルを無効にしました。

ページURL基本タイトル追加タイトル
Home/static_pages/home"Ruby on Rails Tutorial Sample App""Home"
Help/static_pages/help"Ruby on Rails Tutorial Sample App""Help"
About/static_pages/about"Ruby on Rails Tutorial Sample App""About Us"
表3.1サンプルアプリケーションの (ほぼ) 静的なページ。

この節が完了するまでに、3つの静的ページのすべてのタイトルが、“Ruby on Rails Tutorial Sample App | Home”のように、最後の単語のみ表3.1に従ってページごとに変更されるようになります。最初に、リスト3.14のテストを元に、モデルに応じたタイトル表示のテスト (リスト3.18) を追加します。

リスト3.18 タイトルのテスト。
it "should have the right title" do
  visit '/static_pages/home'
  expect(page).to have_title("Ruby on Rails Tutorial Sample App | Home")
end

このテストではhave_titleメソッドを使っています。これは与えられたコンテンツにHTML要素 (タイトル) があるかどうかをチェックします。つまり、以下のコードは

expect(page).to have_title("Ruby on Rails Tutorial Sample App | Home")

titleタグの内容が以下のとおりになっていることを確認します。

"Ruby on Rails Tutorial Sample App | Home"

ここで注意していただきたいのは、与えられた内容に対して完全一致する文字列を使用しなくてはならないということではなく、以下のように部分文字列を指定するだけでもよいということです。

expect(page).to have_title("Home")

上のコードでもタイトル全体とマッチします。

リスト3.18と同じ要領で、StaticPagesのテストに3つの静的ページのテストを追加します (リスト3.19)。テストを追加するにつれて、似たようなコードが繰り返し使用されていることに注目してください。このような重複は5.3.4で取り除きます。

リスト3.19 タイトルのテストを含むStaticPagesコントローラの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

リスト 3.19の内容を反映後、以下を実行します。

$ bundle exec rspec spec/requests/static_pages_spec.rb

今度はテストが赤色 (テストが失敗する) になるはずです。

3.3.2タイトルのテストをパスさせる

今度は、タイトルのテストがパスするようにし、それと同時にWebページを正しく表示させるためのHTMLをすべて追加しましょう。現代的なWebページのマークアップは、基本的に以下のようになっています。

<!DOCTYPE html>
<html>
  <head>
    <title>Greeting</title>
  </head>
  <body>
    <p>Hello, world!</p>
  </body>
</html>

上の構造には次のものが含まれています。1) document type (doctype) は使用するHTMLのバージョン (ここではHTML5)8 をブラウザに対して宣言します。2) headセクション。ここではtitleタグに囲まれた "Greeting" という文字があります。3) bodyセクション。ここではp (paragraph) タグを使って、 “Hello, world!” と表示しています。(なお、HTMLではスペースやタブは無視されるので、インデントはあってもなくても大丈夫です。ただし、インデントがある方がHTMLのデータ構造を理解しやすくなります)。

上のHTML基本構造をHomeページに適用するとリスト3.20のようになります。

リスト3.20 完全な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>

リスト 3.20ではリスト 3.19にある以下のテスト済みのタイトルを使います。

<title>Ruby on Rails Tutorial Sample App | Home</title>

従って、Homeページのテストはパスするはずです。HelpとAboutのテストはまだ赤色 (失敗) の状態ですので、リスト 3.21リスト 3.22のようにコードを追加してテストを緑色 (成功) にします。

リスト3.21 完全なHTML構造を備えたHelpページのビュー
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>
リスト3.22 完全なHTML構造を備えたAboutページのビュー
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>

3.3.3埋め込みRuby

この節ではこれまでに、Railsのコントローラとアクションを使って3つの有効なページを生成することでさまざまなことを達成しました。しかしそれらは単純な静的ページであり、またRailsの能力を十分に発揮できていません。しかも、コードが甚だしく重複しています。

  • ページのタイトルがどれもほぼ同じ (完全にではないが)。
  • “Ruby on Rails Tutorial Sample App” が3つのタイトルで共通している。
  • HTMLの構造全体が各ページで重複している。

このようなコードの重複は、「重複してはならない」(Don’t Repeat Yourself, DRY) という重要な原則に反しています。以後、この節と次の節では重複を取り除き、コードをDRYにしていきます。

上の話と一見矛盾するようですが、最初に、現在のほとんど同じページのタイトルを、完全に同じになるようなコードを追加していきます。こうすることで、すべての重複を一気に取り除くことがより簡単になるからです。

重複を取り除くテクニックの一つとして、ビューで埋め込みRuby (Embedded Ruby) を使用できます。Home、Help、Aboutページには可変の要素があるので、Railsのprovide関数を使用してタイトルをページごとに変更します。それでは、home.html.erbビューのコードを、リスト3.23のように、タイトルに含まれる"Home"という文字を置き換え、動作を確認しましょう。

リスト3.23 タイトルにRubyを埋め込んだ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>

リスト3.23は、ERbと呼ばれている、Rubyの埋め込みの最初の例です (これで、HTMLビューのファイルの拡張子が.html.erbとなっている理由をおわかりいただけたと思います)。 ERbはWebページに動的な要素を加えるときに使うテンプレートシステムです9

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

上のコードに<% ... %>と書かれていることに注目してください。Railsはこの中でprovide関数を呼び出し、:titleというラベルに ’Home’ という文字列を関連付けます10。次に、以下のコードでRubyのyield関数を使用し、それを<%= ... %>表記 (上と微妙に異なることに注目してください) で囲むことにより、テンプレートにタイトルを挿入します11

<title>Ruby on Rails Tutorial Sample App | <%= yield(:title) %></title>

(この2つの埋め込みRubyの違いは次のとおりです。<% ... %>と書くと、中に書かれたコードを単に実行するだけで何も出力しませんが、<%= ... %>と等号を追加して書くと、中のコードが実行され、その結果がテンプレートに挿入されます)。この変更を行なっても、表示されるページの内容は以前とまったく同じです。タイトルの可変部分がERbによって動的に生成されている点だけが異なります。

3.3.1のテストを実行することで、変更が正しく行われたことを確認できます。以下を実行してテストがパスすることを確認しましょう。

$ bundle exec rspec spec/requests/static_pages_spec.rb

続いて、HelpページとAboutページも同様に変更します (リスト3.24リスト 3.25)。

リスト3.24 タイトルで埋め込みRubyを使用したHelpページのビュー
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>
リスト3.25 タイトルで埋め込みRubyを使用したAboutページのビュー
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>

3.3.4レイアウトを使って重複を解消する

タイトルの可変部分をERbを使って置き換えたので、現在それぞれのページはだいたい以下のようになっています。

<% provide(:title, 'Foo') %>
<!DOCTYPE html>
<html>
  <head>
    <title>Ruby on Rails Tutorial Sample App | <%= yield(:title) %></title>
  </head>
  <body>
      Contents
  </body>
</html>

これを見ると、唯一の例外であるbodyタグの内容を除き、すべてのページで (titleタグの内容を含め) 同じ構造になっていることがわかります。

Railsには、共通の構造をまとめるためのapplication.html.erbという特別なレイアウトファイルがあります。このファイル名は3.3.1で変更してあったので、以下のように元に戻します。

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

このレイアウトファイルを有効にするには、デフォルトのタイトル部分を以下の埋め込みRubyのコードに差し替えます。

<title>Ruby on Rails Tutorial Sample App | <%= yield(:title) %></title>

変更の結果、レイアウトファイルはリスト3.26のようになります。

リスト3.26 サンプルアプリケーションのレイアウトファイル。
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 %>

このコードは、各ページの内容をレイアウトに挿入するためのものです。ここでは、このコードの詳細な動作を正確に理解することは重要ではありません。レイアウトを使用するうえでは、/static_pages/homeにアクセスすると、home.html.erbの内容がHTMLに変換され、<%= yield %>の位置に挿入されるということだけ理解しておけば問題ありません。

Railsのデフォルトのレイアウトには、以下の行が追加されていることにも注目してください。

<%= stylesheet_link_tag ... %>
<%= javascript_include_tag "application", ... %>
<%= csrf_meta_tags %>

これらのコードは、スタイルシート、JavaScript、csrf_meta_tagsメソッドをそれぞれページにインクルードするためのものです。スタイルシートとJavaScriptは、Asset Pipeline (5.2.1) の一部です。csrf_meta_tagsは、Web攻撃手法のひとつであるクロスサイトリクエストフォージェリー (cross-site request forgery: CSRF)を防ぐために使われるRailsのメソッドです。

もちろん、リスト3.23リスト3.24リスト3.25のビューには、レイアウトと重複するHTMLがまだ残っているので、それらを削除して、内部のコンテンツだけ残します。これにより、 リスト3.27リスト3.28リスト 3.29にそれぞれ示したように、ビューのコードが美しく簡潔なものになります。

リスト3.27 HTML構造を削除したHomeページ。
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>
リスト3.28 HTML構造を削除したHelpページ。
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>
リスト3.29 HTML構造を削除したAboutページ。
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>

上のように定義されたビューは、Home、Help、Aboutページの表示は以前と変わりませんが、コードの重複が大きく削減されました。最後に、このリファクタリングが正常に行われたことを確認するために、リファクタリング前と同様にテストにパスすることを確認します。

$ bundle exec rspec spec/requests/static_pages_spec.rb

3.4最後に

傍からは、この章では静的ページをほぼ静的ページに変えただけで、ほとんど何もしていないように見えることでしょう。しかし、見た目に反して、Railsのコントローラ、アクション、ビューは大きく改善されており、動的なコンテンツを自由にサイトに追加することができるようになりました。皆さんに残されている今後の課題は、このチュートリアルをいかに最後までやりぬくか、それだけであると言ってよいでしょう。

次の章に進む前に、差分をコミットしてマスターブランチにマージしておきましょう。3.1では、静的ページの開発のためのGitブランチを用意しました。ここまでの作業内容をコミットしていなければ、きりのよい中継点に達したことがわかるようにコミットします。

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

次にmasterブランチに移動し、1.3.5と同じ要領で差分をマージします。

$ git checkout master
$ git merge static-pages

このように中継点まで達したら、コードをリモートリポジトリにアップロードしておくとよいでしょう (1.3.4の手順に従っていれば、リモートリポジトリはGitHubを使用することになるでしょう)。

$ git push

好みに応じて、更新したアプリケーションをHerokuにデプロイしても構いません。

$ git push heroku

3.5演習

  1. サンプルアプリケーションにContact (問い合わせ先) ページを作成してください。リスト3.19に従い、最初に、タイトルが “Ruby on Rails Tutorial Sample App | Contact” となっているかどうかを確認するテストを作成し、それによってURLが/static_pages/contactのページが存在することを確認するテストを作成してください。次に、リスト 3.30の内容をContactページに反映し、これらのテストにパスするようにしてください。(この演習の解答は5.3の説明に含まれています)。
  2. お気付きの方もいると思いますが、リスト3.19に示されている、StaticPagesコントローラのspecテストには若干の重複があります。特に、“Ruby on Rails Tutorial Sample App”というタイトルはそれぞれのタイトルのテストで繰り返し記述されています。引数として渡されたシンボルと同名の変数にブロックの評価値を格納する、RSpecのlet関数を使い、リスト 3.31のようにテストを変更し、テストにパスするようにしましょう。リスト3.31では文字列の式展開 (string interpolation) が行われていますが、これについては4.2.2で紹介します。
  3. (上級者向け)The Twelve-Factor App」のページでも説明されているように、互換性の問題によるエラーを最小限にとどめるために、開発/テスト/本番環境で共通のデータベースを使うことは良いことです。「Heroku instructions for local PostgreSQL installation」には、PostgreSQLをローカル環境にインストールする手順が紹介されてます。PostgreSQLを使う場合は、 リスト3.32に示したように、Gemfileからsqlite3 gemを削除し、pg gemのみを使うようにしてください。さらに、config/database.ymlファイルと、PostgreSQLをローカル環境で動作させる方法を学ぶ必要があります。以上の情報を元に、PostgreSQLを使用して開発データベースとテストデータベースを作成し、それぞれ設定を行うことが、この課題のゴールです。PostgreSQLデータベースへの接続と内容表示には、Inductionというツールが便利です。警告: この演習はかなり難易度が高いので、上級者にのみお勧めします。もし行き詰まってしまったら、すぐにこの演習を飛ばして次の作業に進んでください。なお、既に説明したとおり、このチュートリアルで開発しているサンプルアプリケーションは、SQLiteとPostgreSQLのどちらについても完全に互換性があります。
リスト3.30 Contactページで使用するコンテンツのコード。
app/views/static_pages/contact.html.erb
<% provide(:title, 'Contact') %>
<h1>Contact</h1>
<p>
  Contact Ruby on Rails Tutorial about the sample app at the
  <a href="http://railstutorial.jp/contact">contact page</a>.
</p>
リスト3.31 基本となるタイトルを含むStaticPagesコントローラのspec。
spec/requests/static_pages_spec.rb
require 'spec_helper'

describe "Static pages" do

  let(:base_title) { "Ruby on Rails Tutorial Sample App" }

  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("#{base_title} | 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("#{base_title} | 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("#{base_title} | About Us")
    end
  end

  describe "Contact page" do

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

    it "should have the title 'Contact'" do
      visit '/static_pages/contact'
      expect(page).to have_title("#{base_title} | Contact")
    end
  end
end
リスト3.32 SQLiteの代わりにPostgreSQLを使う場合のGemfile
source 'https://rubygems.org'
ruby '2.0.0'
#ruby-gemset=railstutorial_rails_4_0

gem 'rails', '4.0.5'
gem 'pg', '0.15.1'

group :development, :test do
  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 'rails_12factor', '0.0.2'
end

3.6高度なセットアップ

3.2でも簡単に説明しましたが、rspecコマンドを直接実行することは実用的ではありません。この節では、最初にbundle execを毎回コマンドに入力せずに済む方法を紹介します。次に、3.6.2でGuardを使用したテストスイートの自動化方法を紹介し、さらに3.6.3でSporkを使用したテストスイートの自動化方法も紹介します。最後に、Sublime Text上で直接テストを実行する方法を紹介します。このテクニックは、特にSporkと併用すると非常に便利です。

この節は、ほとんどが上級者向けの内容になっており、この節を飛ばしても次の章以降には何の影響もありません。この節の内容は先進的ですが、その分、本書の他の内容よりも陳腐化しやすいので、ご利用のシステムでこの節の例が完全に動作するとは限りません。この点をご了承願います。完全に動作させるには、Googleで最新情報を検索して調べることが必要になるでしょう。

3.6.1bundle execを追放する

3.2.1でも簡単に紹介しましたが、rakerspecコマンドを実行するときには、現在の環境に依存するgemをGemfileから読み込んでプログラムを実行するために、bundle execをコマンドの前に追加する必要があります (技術的な理由により、railsコマンドだけは例外です)。この節の作業はかなり厄介です。また、bundle execの入力を省略する方法を2とおりの方法で説明します。

RVM Bundler の統合

最初に、お勧めの方法としてRVMを使う方法を紹介します。RVMはバージョン1.11以降からBundlerとの統合が含まれています。最初に、以下に従ってRVMのバージョンを最新にします。

$ rvm get stable
$ rvm -v

rvm 1.19.5 (stable)

バージョン1.11.x以降のRVMを使用していれば、インストールされたgemは自動的に適切なBundlerの環境で実行されますので、それ以上何もしなくても以下のようにbundle execを省略して実行できます。

$ rspec spec/

bundle execを省略することができました。 このとおりにできた場合は、この節の残りはスキップしてください。

新しいバージョンのRVMを使うことができない場合は、Ruby Version Managerがローカル環境で自動的に設定する適切な実行ファイルがあれば、RVM Bundler integration12bundle execを省略することができます。一見奇妙ですが、実行方法は簡単です。最初に以下の2つのコマンドを実行します。

$ rvm get head && rvm reload
$ chmod +x $rvm_path/hooks/after_cd_bundler

次に以下のコマンドを実行します。

$ cd ~/rails_projects/sample_app
$ bundle install --without production --binstubs=./bundler_stubs

魔法のように見えますが、これらのコマンドでRVMとBundlerを統合できます。そして、rakerspecなどのコマンドを適切な環境で自動的に実行してくれます。bundler_stubsディレクトリを.gitignoreファイルに追加します (リスト3.33)。これらのファイルはローカル環境でしか使用しないためです。

リスト3.33 .gitignoreファイルにbundler_stubsを追加する。
# 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
bundler_stubs/

3.6.2guardなど、他の実行ファイルを追加する場合は、bundle installコマンドを再度実行してください。

$ bundle install --binstubs=./bundler_stubs

binstubsオプション

RVMを使用していない場合でも、 rubygems-bundlerというgemを使うことで、bundle execを省略することができます。

$ gem install rubygems-bundler
$ gem regenerate_binstubs

上記の2つ目のコマンドを実行すると、ローカル環境に必要な全ての設定を自動で作成してくれます。このため、今後は以下のようにテストスイートを実行できます。

$ rspec spec/

同様にrakeなども以下のように実行できます。

$ rake db:migrate

3.6.2guardなど、他の実行ファイルを追加した場合は、gem regenerate_binstubsコマンドを再実行する必要があるかもしれません (ほとんどの場合、再実行しなくても問題ないとは思いますが、念のため)。

本書の以後の章では、この節をスキップする方に配慮して明示的にbundle execを与えてコマンドを実行するようにしています。もちろん、ご利用のシステムが適切に設定されていれば、bundle execを省略しても構いません。

3.6.2Guardによるテストの自動化

rspecコマンドは、テストのたびにコマンドラインに移動して手動でコマンドを実行しなければならない点が面倒です (もうひとつ、テストスイートの起動が遅いという問題がありますが、これは3.6.3で対応します)。この節では、テストを自動化するGuardというgemの使い方を紹介します。Guardはファイルシステムの変更を監視し、たとえばstatic_pages_spec.rbファイルを変更すると自動的にテストを実行します。さらに、home.html.erbファイルが変更されると static_pages_spec.rbが自動的に実行されるようにGuardを設定することもできます。

最初に、リスト3.34のように、guard-rspecGemfileに追加します。

リスト3.34 サンプルアプリケーションのGemfileにGuardを追加する。
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'
  gem 'guard', '2.6.1'
  gem 'guard-rspec', '2.5.0'
end

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

  # Uncomment this line on OS X.
  # gem 'growl', '1.0.3'

  # Uncomment these lines on Linux.
  # gem 'libnotify', '0.8.0'

  # Uncomment these lines on Windows.
  # gem 'rb-notifu', '0.0.4'
  # gem 'win32console', '1.3.2'
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

上のコードを使用する際は、testグループ内で自分のシステムに該当する行を必ずコメント解除してください (Mac用のGrowlの通知機能を使用するのであれば、GrowlをAppleのApp Storeで購入する必要があります。値段は大したことはありません)。

次にbundle installを実行してgemをインストールします。

$ bundle install

Guardを初期化し、RSpecと一緒に動作するようにします。

$ bundle exec guard init rspec
Writing new Guardfile to /Users/mhartl/rails_projects/sample_app/Guardfile
rspec guard added to Guardfile, feel free to edit it

結合テストとビューが更新されたら自動的に適切なテストが実行されるように、生成されたGuardfileを編集します (リスト3.35)。

リスト3.35 デフォルトのGuardfileに追記する。requireが追加されていることに注意。
require 'active_support/inflector'

guard 'rspec', all_after_pass: false do
  .
  .
  .
  # Custom Rails Tutorial specs
  watch(%r{^app/controllers/(.+)_(controller)\.rb$})  do |m|
    ["spec/routing/#{m[1]}_routing_spec.rb",
     "spec/#{m[2]}s/#{m[1]}_#{m[2]}_spec.rb",
     "spec/acceptance/#{m[1]}_spec.rb",
     (m[1][/_pages/] ? "spec/requests/#{m[1]}_spec.rb" :
                       "spec/requests/#{m[1].singularize}_pages_spec.rb")]
  end
  watch(%r{^app/views/(.+)/}) do |m|
    (m[1][/_pages/] ? "spec/requests/#{m[1]}_spec.rb" :
                      "spec/requests/#{m[1].singularize}_pages_spec.rb")
  end
  watch(%r{^app/controllers/sessions_controller\.rb$}) do |m|
    "spec/requests/authentication_pages_spec.rb"
  end
  .
  .
  .
end

上のコードにある以下の行に注目してください。

guard 'rspec', all_after_pass: false do

これは、テストのパスに失敗した後に、他の余分なテストが実行されないようにするためのものです (Red-Green-Refactorのサイクルを早めるため)。

以上の準備が終われば、以下のコマンドでguardを起動できます。

$ bundle exec guard

bundle execを省略するには、3.6.1の手順を再度実行してください。

spec/routingディレクトリが見つからないというエラーが表示された場合は、以下のように空のディレクトリを作ることで回避できます (訳注: 同様にして、spec/controllersのエラーが表示された場合も$ mkdir spec/controllersと実行することで解決できます)。

$ mkdir spec/routing

3.6.3Spork を使ったテストの高速化

bundle exec rspecを実行すると、テストが開始されるまでしばらく時間がかかることにお気付きかもしれません。テストがいったん開始されればすぐに終了します。これは、RSpecを実行するたびにRailsの環境全体を読み込み直す必要があるためです。Sporkテストサーバー13はこの問題を解決するためのものです。Sporkは環境を1回だけ読み込み、今後実行するテストのためのプロセスを管理します。Sporkは3.6.2のGuardと組み合わせるとさらに便利です。

最初に、sporkに依存するgemをGemfileに追加します (リスト3.36)。

リスト3.36 サンプルアプリケーションのGemfileにSporkを追加する。
source 'https://rubygems.org'
ruby '2.0.0'
#ruby-gemset=railstutorial_rails_4_0

gem 'rails', '4.0.5'

group :development, :test do
  .
  .
  .
  gem 'spork-rails', '4.0.0'
  gem 'guard-spork', '1.5.0'
  gem 'childprocess', '0.3.6'
end
.
.
.

次に、bundle installでSporkをインストールします。

$ bundle install

次に、Sporkの設定にbootstrapを指定します。

$ bundle exec spork --bootstrap

そして、環境の読み込みを一回で済ますため、spec/spec_helper.rbの中で環境をpreforkのブロックで読み込むように、RSpecの設定を変更する必要があります (リスト 3.37)。

リスト3.37 Spork.preforkブロックへの環境読み込みを追加する。
spec/spec_helper.rb
require 'rubygems'
require 'spork'

Spork.prefork do
  ENV["RAILS_ENV"] ||= 'test'
  require File.expand_path("../../config/environment", __FILE__)
  require 'rspec/rails'
  require 'rspec/autorun'

  # Requires supporting ruby files with custom matchers and macros, etc,
  # in spec/support/ and its subdirectories.
  Dir[Rails.root.join("spec/support/**/*.rb")].each {|f| require f}

  # Checks for pending migrations before tests are run.
  # If you are not using ActiveRecord, you can remove this line.
  ActiveRecord::Migration.check_pending! if defined?(ActiveRecord::Migration)

  RSpec.configure do |config|
    # == Mock Framework
    #
    # If you prefer to use mocha, flexmock or RR, 
    # uncomment the appropriate line:
    #
    # config.mock_with :mocha
    # config.mock_with :flexmock
    # config.mock_with :rr

    # Remove this line if you're not using ActiveRecord
    # or ActiveRecord fixtures
    config.fixture_path = "#{::Rails.root}/spec/fixtures"

    # If you're not using ActiveRecord, or you'd prefer not to run each of 
    # your examples within a transaction, remove the following line or
    # assign false instead of true.
    config.use_transactional_fixtures = true

    # If true, the base class of anonymous controllers will be inferred
    # automatically. This will be the default behavior in future versions of
    # rspec-rails.
    config.infer_base_class_for_anonymous_controllers = false

    # Run specs in random order to surface order dependencies. If you 
    # find an order dependency and want to debug it, you can fix the
    # order by providing the seed, which is printed after each run.
    #     --seed 1234
    config.order = "random"
    config.include Capybara::DSL
  end
end

Spork.each_run do
  # This code will be run each time you run your specs.

end

Sporkを起動する前に、以下のようにテストスイートを実行して、基準となる実行時間を測定します。

$ time bundle exec rspec spec/requests/static_pages_spec.rb
......

6 examples, 0 failures

real	0m8.633s
user	0m7.240s
sys	0m1.068s

上の実行結果では、実際のテストは1/10秒以下で実行されますが、テストスイートは7秒以上かかっています。実行時間のスピードアップのため、別のターミナルウィンドウを開いてアプリケーションのルートディレクトリに移動し、以下のようにSporkサーバーを起動します。

$ bundle exec spork
Using RSpec
Loading Spork.prefork block...
Spork is ready and listening on 8989!

bundle execを省略するには、3.6.1の手順を再度実行してください。別のターミナルウィンドウでは、--drb (“distributed Ruby” の略) オプションを追加してテストスイートを実行すると、環境読み込みのオーバーヘッドが大幅に減少していることが確認できます。

$ time bundle exec rspec spec/requests/static_pages_spec.rb --drb
......

6 examples, 0 failures

real	0m2.649s
user	0m1.259s
sys	0m0.258s

rspecを実行するたびに--drbオプションを追加するのは不便なので、アプリケーションのルートディレクトリにある.rspecファイルにリスト3.38の内容を記載することをお勧めします。

リスト3.38 自動的にSporkを使うためのRSpecの設定。
.rspec
--colour
--drb

Sporkを使う上でひとつ注意があります。preforkで読み込むファイル (たとえばroutes.rb) が変更された場合、Sporkサーバーを再起動して新しいRailsの環境を再度読み込む必要があります。パスするはずのテストが失敗した場合は、Ctrl-CでSporkサーバーを停止して再起動してください。

$ bundle exec spork
Using RSpec
Loading Spork.prefork block...
Spork is ready and listening on 8989!
^C
$ bundle exec spork

GuardにSporkを導入する

SporkはGuardと併用すると非常に便利です。設定を行うと、以下のようにコマンド上で併用することができます。

$ bundle exec guard init spork

さらに、リスト3.39に示したようにGuardfileを変更する必要があります。

リスト3.39 Spork向けに更新したGuardfile
require 'active_support/inflector'

guard 'spork', :cucumber_env => { 'RAILS_ENV' => 'test' },
               :rspec_env    => { 'RAILS_ENV' => 'test' } do
  watch('config/application.rb')
  watch('config/environment.rb')
  watch('config/environments/test.rb')
  watch(%r{^config/initializers/.+\.rb$})
  watch('Gemfile')
  watch('Gemfile.lock')
  watch('spec/spec_helper.rb') { :rspec }
  watch('test/test_helper.rb') { :test_unit }
  watch(%r{features/support/}) { :cucumber }
end

guard 'rspec', after_all_pass: false, cli: '--drb' do
  .
  .
  .
end

guardの変数に:cli => --drbが追加されました。この設定により、Guardがコマンドラインインタフェース (CLI) からSporkサーバーを使うようになります。上の設定には、第5章から使用するspec/support/ディレクトリを監視するコマンドも追加してあります。

設定が完了したら、以下のguardコマンドでGuardとSporkを同時に起動します。

$ bundle exec guard

Guardは自動的にSporkサーバーを起動するため、テスト実行時のオーバヘッドを劇的に削減できます。

Guard、Spork、テスト通知機能 (オプション) を使用して便利なテスト環境を構築することで、テスト駆動開発がやみつきになることでしょう。詳細については、Railsチュートリアルのスクリーンキャスト14を参照してください。

3.6.4Sublime Text上でテストする

Sublime Textを使用していれば、エディタの中から直接テストを実行できる強力なヘルパーコマンドを利用できます。このテスト方法は著者のお気に入りです。最終的に大規模にスケールアップする可能性のある少数のテストを、長期間に渡って実施したいような場合に非常に有用であるためです。このコマンドを利用するには、Sublime Text 2 Ruby Tests15に記載されている、自分の環境向けの説明に従って設定を行なってください。著者の環境 (Macintosh OS X) の場合、以下のようにコマンドをインストールします。

$ cd ~/Library/Application\ Support/Sublime\ Text\ 2/Packages
$ git clone https://github.com/maltize/sublime-text-2-ruby-tests.git RubyTest

参考: Rails Tutorial Sublime Text16にある説明に従って設定することもできます。

Sublime Textを再起動すると、以下のようなコマンドがRubyTestパッケージによって提供されます。

  • Command-Shift-R: itブロック内のテストについては、テストを1回だけ実行します。describeブロック内のテストについては、一連のテストを実行します。
  • Command-Shift-E: 最後に実行したテストを再度実行します。
  • Command-Shift-T: 現在のファイルにあるテストをすべて実行します。

比較的小さいプロジェクトであってもテストスイートの実行には時間がかかるため、テストを一度に1つだけ実行したり、小規模なテストグループだけを実行したりできるのは大きな長所です。従来のテストでは、たった1つのテストを実行するだけでも、Railsの環境に匹敵するオーバーヘッドが発生してしまいました。このため、上述のテスト用コマンドとSporkの組み合わせは非常に相性が良いのです。Sporkは、テストを実行するたびに発生していたテスト環境起動によるオーバーヘッドを取り除いてくれるため、1つのテストを実行するたびにすべてをテストするのと同程度のオーバーヘッドが発生するようなことがなくなります。個人的には、以下のテスト手順がお勧めです。

  1. ターミナルウィンドウでSporkを起動する。
  2. テストを1つ (または小規模なテストグループ) を作成する。
  3. Command-Shift-Rコマンドでテストが失敗することを確認する。
  4. 対応するアプリケーションコードを作成する。
  5. Command-Shift-Eコマンドで上のテストと同じテストを実行し、今度は成功することを確認する。
  6. 2-5の手順を繰り返す。
  7. 中継点 (コミットの直前など) に到達したら、 コマンドラインでrspec spec/を実行してテストスイートをすべて実行し、成功することを確認する。

Sublime Textの中からテストが実行できることはもちろん便利ですが、場合によってはGuardの方が便利なこともあると思います。上の手順は、著者が個人的に常用しているテスト駆動開発のテクニックとしてご紹介しました。

  1. CapybaraはWebratの後続プロジェクトであり、世界最大の齧歯類が名前の由来です。
  2. なお、installは省略可能です。実は、bundleコマンドはbundle installのエイリアスでもあります。
  3. システムによっては、リスト1.7の.gitignoreを参考にして、さらに便利な設定にすることもできます。
  4. https://github.com/railstutorial/sample_app_rails_4 
  5. ここで静的なページを作るために採用した方法は、おそらく最もシンプルな方法です。ただし他にも方法はあります。最適な方法は状況によって異なり、たとえば極めて多数の静的なページを1つのStaticPagesコントローラだけまかなおうとすると重荷になる可能性があります。今回はいくつかの静的なページを作るだけなので、重荷にはなりません。
  6. 毎回bundle execを追加するのは面倒ですが、3.6のオプションを使うと省略できます。
  7. 筆者は普段、ターミナルやテキストエディタの背景は黒色にしていますが、明るい色の背景の方がスクリーンショットの見栄えが良いので、(一時的に) 明るい背景を使用しています。
  8. HTMLは常に変化しています。doctypeを明示的に宣言することで、今後もブラウザが正しくページを描画してくれる可能性が高まります。極めてシンプルなdoctype宣言である<!DOCTYPE html>は、最新の標準HTML (HTML5) であることを示しています。
  9. 2番目によく使われているHamlというテンプレートシステムもあります。Hamlは個人的に非常に気に入っているのですが、初心者向けのチュートリアルで使うにはやや標準から外れています。
  10. Railsでの開発経験者であれば、この時点でcontent_forの使用を検討すると思いますが、残念ながらAsset Pipelineと併用すると正常に動作しないことがあります。provide関数はcontent_forの代替です。
  11. Rubyを勉強したことのある方であれば、Railsはブロックの内容をyieldしていると推測することでしょう。そして、その推測はおそらく正しいでしょう。しかし、Rails開発のためにこれらの詳細を知る必要はありません。
  12. https://rvm.io/integration/bundler 
  13. sporkはspoon-forkを組み合わせた造語です。このプロジェクト名は、POSIXforkにおけるSporkの用法をもじったものです。
  14. http://railstutorial.jp/screencasts 
  15. https://github.com/maltize/sublime-text-2-ruby-tests 
  16. https://github.com/mhartl/rails_tutorial_sublime_text 
Railsチュートリアルは,Ruby/Rails のアジャイル開発を得意とする YassLab によって運営・保守されております.
継続的に良いコンテンツを提供する為に,電子書籍解説動画のご購入を検討して頂けると幸いです m(_ _)m
スポンサーシップや商用利用などに関するご相談がありましたら,お問い合わせページよりお気軽にご連絡ください :)