Ruby on Rails チュートリアル

実例を使ってRailsを学ぼう

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

目次

  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.
 * ----------------------------------------------------------------------------
 */

第1章 ゼロからデプロイまで

Ruby on Railsチュートリアルへようこそ。本書は「Ruby on Railsを使ったWeb開発を学びたいのだけれど、何から始めればよいのかを教えて欲しい」という要望に応えるために作成されました。本書を読み終わる頃には、自分の手でWebアプリケーションを開発するための技術のみならず、デプロイ(deploy: 本番環境でアプリを動かし、一般公開すること)を行なうための技術もすべて習得できます。Railsの教育エコシステムにはさらに高度な内容の書籍、ブログ、スクリーンキャストなどが数多くありますが、それらを読み解く力も身につきます。Ruby on Railsチュートリアルは最新のRails 4を使用していますので、Web開発の最前線を本書で学ぶことができます。(Ruby on Railsチュートリアルが最新版かどうかについては、本書の公式サイト(http://railstutorial.jp/)でチェックすることができます。特に、本書をオフラインで読んでいる方は、本書が最新版かどうかをhttp://railstutorial.jp/にあるオンライン版のRailsチュートリアルでご確認ください。

(: 今皆様が読んでいるこのチュートリアルは、Rails 4.0というバージョンに対応しています。本書はRails 3.2からRail 4.0へのバージョンアップにともなって改定されましたが、チュートリアルとしての版(エディション)は更新されていないことに注意してください。Rails 4.0に対応するために行った変更の量がわずかで、本書の版を改めるには及ばないと判断したためです。本書はWeb開発へといざなう「チュートリアル」に徹しており、Railsの高度な内容にまでは踏み込んでいません。以前のRails 3.2対応版と今回のRails4.0対応版との差分が少ないのは、このような理由によるものです。もちろん、コラム1.1でも紹介しているとおり、細かな変更は多数あります。しかし、その中で重要な変更と呼べるのは、7.3.2に追加したStrong Parametersと呼ばれるセキュリティ関係の新しい機能だけです。今後Railsで重要な変更が生じることがあれば、Railsチュートリアルの版を改訂するつもりです。新しい版では、TurbolinksRussian doll caching、RSpec の新機能「feature specs」などについても取り上げる予定です。)

本書の目的は、単にRailsを学ぶことではではなく、Railsを例にとって広い意味でのWebアプリケーション開発そのものを学ぶことにあります。つまり、Railsに限らず、World Wide Web上で動くソフトウェアを開発したり改良したりするための技術を習得することを目指します。したがって、Ruby on Railsの他に、HTML、CSS、データベース、バージョン管理、テスト、デプロイなどの多くの技術についても本書で学びます。この目的を達成するため、Ruby on Rails チュートリアルでは、統合的なアプローチを採用しています。つまり、「実際に動くサンプルアプリケーションをゼロから開発する」という例題をこなすことによって、Railsについて総合的に学ぶのです。Derek Siversが前書きで述べているように、本書は一本道のストーリーで構成されていて、最初から最後まで飛ばさずに読むことを前提としています。技術書を飛ばし読みするのが習慣になっている方にとっては少々頭の切り替えが必要になるかもしれませんが、それだけの値打ちは十分にあります。ぜひトライしてみてください。Ruby on RailsチュートリアルをTVゲームにたとえると、あなたは主人公であり、各章をクリアするたびにRails開発者としてレベルアップしていくと考えてください (演習は中ボスです)。

第1章では、最初に必要なソフトウェアをインストールし、開発環境 (1.2) を整えて Ruby on Railsを動かす準備をします。次にfirst_appという最初の Rails アプリの作成に着手します。Rails チュートリアル では、実戦的なソフトウェア開発のベストプラクティスを重要視しているので、新しいRailsプロジェクトを作成した直後にGit(1.3) を使ったバージョン管理を行います。最後に、作成したアプリを本番 (production) 環境 (1.4) にデプロイして一般公開するところまで実践します。

第2章では、Railsアプリケーションの基本的な仕組みを体験してもらうために、別のプロジェクトを作成します。このデモアプリ(demo_app)を短時間で作成するために、scaffold (コラム1.2) を使ってコードを自動生成します。ただし、scaffoldで自動生成されたコードは、込み入っていて美しくありません。このため、第2章では自動生成されたコードについては解説せず、生成されたURI (いわゆる URL1) がどのように動くのかをWebブラウザで確かめることに焦点を当てます。

第3章以降では、1つの大きなサンプルアプリケーション (sample_app )を開発します。コードは自動生成せず、ゼロからコードを書き進めます。さらに、このサンプルアプリケーションからTDD (テスト駆動開発) という開発手法を導入します。第3章では、静的ページと若干の動的コンテンツを追加するところから始めます。次の第4章では少し回り道をし、Railsを支えているRubyという言語について簡単に学びます。第5章から第9章にかけて、レイアウト、ユーザーのデータモデル、ユーザー登録・認証システムを順に作成し、サンプルアプリケーションの基本的な部分を実装します。最後の第10章第11章では、マイクロブログ機能とソーシャル機能を実装し、実際に動作するWebサイトを完成させます。

最終的なサンプルアプリケーションは、(偶然にも、かつてはRailsで実装されていた) 某ソーシャルマイクロブログサイトにとてもよく似た仕上がりになります。なお、Rails チュートリアルでは、サンプルアプリケーションの開発を中心にして話が進められていきますが、(サンプルアプリケーションに特化した技術ではなく) Web アプリケーションの開発で必要になる一般的な技術を重点的に解説していきます。このため、本書を読み終わる事には、あなたが今後どんな Web アプリケーションを開発したいのかに関わらず、どんな開発にも通用する基礎的な力を身につけることができます。

1.1はじめに

Ruby on Railsは2004年にデビューして以来、急速に成長していきました。現在では、動的なWebアプリケーションを開発するフレームワークとして、最も有力かつ人気のあるフレームワークの1つになっています。小さなスタートアップから巨大企業まで、多くの組織が Rails を使っています。例えば、37signalsGitHubShopifyScribdTwitterDisneyHuluYellow Pages など、Ruby on Rails を使っているサイトのリストは増える一方です。他にも、ENTPthoughtbotPivotal LabsHashrocketといった、Railsを専門的に扱う会社も数多くあります。また、Railsを専門にしたフリーランスのコンサルタントやインストラクター、開発者については数えきれません。

Railsがこれだけ多くに人達に使われているのはなぜでしょうか。理由の1つは、Railsが100%オープンソースで、制限の緩いMITライセンスで公開されているからです。これにより、Railsをダウンロードしたり使ったりする際のコストが一切かかりません。また、Railsの設計が簡潔で美しいことも、Railsの成功を支えている秘訣の1つです。これを実現できたのは、Railsの背後で動いているRuby言語の驚異的な柔軟性のおかげです。具体的には、Webアプリケーションの作成に特化したDSL (ドメイン固有言語) を Ruby言語で実装していることにより、HTMLやデータモデルの生成、URL のルーティングなど、Webプログラミングで必要な多くの作業が簡単になります。その結果、Railsを使用してアプリケーションを開発すると、コードが簡潔になり、読みやすくなります。

さらに、Railsは最新のWebテクノロジーやフレームワーク設計に素早く適応しています。たとえば、Rails は「REST」という設計思想の重要性をいち早く理解し、対応したフレームワークの1つです (RESTについては後で解説します)。また、他のフレームワークで何か新しい技術が成功すれば、Railsの創始者であるDavid Heinemeier HanssonRailsのコアチームは、そうしたアイデアを常に積極的に取り入れています。印象的な例として、かつて互いにライバルの関係にあったMerbとRailsとの統合が挙げられます。この統合の結果、RailsはMerbのモジュール設計や安定したAPI、そしてパフォーマンスの向上など多くの恩恵を受けることができました。

最後に、Railsには驚くほど熱心かつ多様なコミュニティという素晴らしい財産があります。Railsコミュニティには、何百人ものオープンソースコントリビュータ達や、多くの参加者で賑わうカンファレンス、膨大な数のgem (ページネーションや画像アップロードといった特定の問題を解決するためのgemなど)、多くの情報を発信し続けるブログ、掲示板、IRCがあります。このような熱心で多様なコミュニティのおかげで、開発中に何らかのエラーに遭遇したとしても、エラーメッセージをそのままGoogleで検索するだけで、関連するブログ記事や掲示板のスレッドをたちどころに見つけることができます。

1.1.1 読者の皆さまへ

Railsチュートリアルは、統合的な構成を採用しています。構成の中心となるのはもちろんRailsですが、他にもRuby言語やRSpecを使ったテスト、HTMLCSS、若干のJavaScriptSQLのチュートリアルが含まれています。チュートリアルが統合的に構成されていることにより、本書を最後まで完了することができれば、現時点のWeb開発の知識量に関わらず、より高度な情報源を難なく読みこなす実力が身につき、上に挙げたJavaScriptなどのRails以外のトピックについても理解しやすくなります。言い換えると、本書で扱うトピックの範囲がそれだけ広いということでもあります。プログラミング経験の浅い方にとっては、その情報量の多さに圧倒されるかもしれません。そこで、読者の経験や知識に応じた、Railsチュートリアルを読破するためのアドバイスを用意しました。ぜひ参考にしてください。

【すべての読者へ】 「Railsを学ぶ前にRubyを勉強した方がよいでしょうか?」は定番の質問です。この質問への回答は、読者の学習スタイルやプログラミング経験次第で異なるため、一口には言えません。Web開発を最初から体系的に学びたい場合や、プログラミングの経験が全くない場合は、Rubyを最初に学ぶのがよいでしょう。Rubyを学ぶのであれば、Peter Cooperの「Beginning Ruby」がお勧めです。これからRailsで開発を始めようとする人は「とにかくWebアプリケーションを作りたい!」と考えていることがほとんどなので、たった1つのWebページを作成するために500ページものRuby本を読み通す気にはなれないでしょう。Rubyを急いで学びたいのであれば、Rubyを対話的に学習できるWebサービス「Try Ruby2」を使ってRubyをおおまかに理解しておくことをお勧めします。必要であれば、Rails for Zombies3に目を通してRailsの全体像を把握しておくとよいかもしれません (訳注: いずれも英語版のみ)。

「最初からテストを書かないといけないでしょうか?」も定番の質問です。「はじめに」で述べたように、Railsチュートリアルでは最初からテスト駆動開発 (TDD) を採用しています。その理由は、著者の開発経験に基づき、テスト駆動開発こそがRailsアプリケーションの開発に最も適切な手法だと考えられるからです。ただし、テスト駆動開発を全面的に採用したことにより、本チュートリアルはその分複雑になり、読者の負担も増えています。Railsのコードだけでなく、テストコードも書かなくていけないからです。そこで、もしテストで行き詰まってしまった場合には、そこで長々と悩むよりも思い切って (一周目は) その箇所をスキップしてしまいましょう。よりお勧めの方法は、テストコードの完全な理解については欲張らずに後回しにし、本書のテストは自分の書いたコードが正しく動くかどうかの検証ツールと割り切って使うことです。具体的には、specと呼ばれるテストファイルを作成し、そのファイルに、本書で指示された通りにテストコードをコピー&ペーストするだけにしておくのです。第5章で詳しく説明しますが、テストスイート (複数のテストケースを束ねたもの) を実行すると、最初はテストに失敗します (この時点では失敗するのが正しいのです)。失敗することを確認したら、チュートリアルに従って、今度はアプリケーションのコードを書きます。最後に、同じテストスイートをもう一度実行すると、今度はテストに成功するようになっています。つまり、最終的にテストが成功すれば、自分が書いたコードは正しく動いていることが確認できたことになります。

【初めてプログラミングを始める方へ】 残念ながら、Railsチュートリアルは、まったくのプログラミング初心者を対象にして書かれたものではありません。どれほど単純なWebアプリケーションであっても、それを作るために理解しておかなければいけないことがたくさんあり、そうした予備知識は省略できないからです。Webプログラミングをまったくしたことのない初心者の方が、このRailsチュートリアルを難しすぎると感じた場合は、本書を学ぶ前にまずHTMLやCSSの基礎を勉強してください。それらの基礎が身についたら、是非もう一度Railsチュートリアルに挑戦してください。(HTML関連ではこれといったお勧めの教材は思いつかなかったのですが、「Head First HTML」はなかなかよさそうです。David Sawyer McFarland著「CSS: The Missing Manual」を推薦してくれた読者もいます。)Peter Cooper著「Beginning Ruby」の最初の2、3章をやってみるのもよいでしょう。同書で扱っているサンプルアプリケーションは、本格的なWebアプリケーションに比べて非常に小さいので、比較的やりやすいと思います。この節を本書に追加したのは、まったくの初心者の方々がいきなり本書でWeb開発を学ぼうとしているケースが実に多かったからです。そうした方々が本書に挑むこと自体はもちろんOKです。最も大事なのは情熱なのですから。最後に、そうした方々に特にお勧めな教材として「Railsチュートリアルのスクリーンキャストのシリーズ4」をご紹介いたします (訳注: 日本語版もリリースされました!)。この動画版Railsチュートリアルでは、Railsを使ったソフトウェア開発の作業を、まるで「達人の肩越しに」のぞきこんでいるかのように見て学ぶことができます。

【プログラミングの経験があり、これからWeb開発を始める方へ】 既にクラスやメソッド、データ構造などの概念に馴染みがある方であれば、それらの知識は大いに役立ちます。JavaやC/C++といった言語とRuby言語では記法が異なるので、最初は少し違和感があるかもしれませんが、すぐにRuby言語の記法に慣れるでしょう。(Rubyでは行末にセミコロンを置いても置かなくても問題なく動きますので、行末にセミコロンがないとどうしても落ち着かない方はそうしてみてもよいかもしれません。お勧めはしませんが。)Rails チュートリアルでは、覚えておく必要のあるWeb特有の概念をすべてカバーしているので、POSTPATCHの違いが分からなくても今は心配する必要はありません。

【Web開発の経験があり、これからRailsを始める方へ】 PHPやPythonなどの動的言語の経験があれば、本書を楽に読み進められるはずです。特にPythonの経験は非常に有利です。本書でカバーする基本的な概念については既に知っていると思いますが、テスト駆動開発やREST構造などの概念については馴染みがない方もいるでしょう。Rubyには独特のクセがあるので、多少戸惑うこともあるかもしれません。

【Rubyプログラミングの経験があり、これからRailsを始める方へ】 最近ではRubyプログラマがRailsを使うのは当たり前になりつつあるので、純粋にRubyのみを使っている方は少ないと思います。生粋のRubyプログラマであれば、本書を楽に読み進められるでしょう。本書を読み終わった頃には、すぐにも独自のアプリケーションを開発できるでしょう。

【Railsを使った経験はあるが、まだ十分ではない方へ】 既に他のチュートリアルを読んだり、小規模なRailsアプリをいくつか作った経験がおありだと思います。読者からのフィードバックを読んだ限りでは、そのような方であっても本書から多くのことを学べるはずだと自信を持って言えます。本書には、そうした方々が初めてRailsを学んだ頃よりも新しい技術がふんだんに盛り込まれているため、本書で学ぶ技術はきっと役に立つはずです。

【Rails経験の豊富な方へ】 Railsを使った開発の経験が既に十分にあるのであれば本書は必要ないはずなのですが、意外にも多くのRails開発者から「思ったよりも役に立った」との声を頂いているので、Rails開発のベテランであってもRailsを違う視点から見ることで楽しめるかもしれません。

最後に、Ruby on Railsチュートリアルを読み終えた経験豊富なプログラマの方には、さらに高度な話題を扱っている「The Well-Grounded Rubyist」(David A. Black 著) または「Eloquent Ruby」(Russ Olsen 著) をお勧めします。あるいは、トピック別に書かれている「The Ruby Way」(Hal Fulton 著) を読んでみるのも良いでしょう。

本書を読み終える頃には、本書を読む前の知識量の多少にかかわらず、より高度なRailsの情報源を理解できるようになっているはずです。その中でも特にお勧めのRailsの情報源を紹介します。

1.1.2 Railsと「スケール」について

本節を続ける前に、一旦ここで、Railsフレームワークに対して初期の頃から 指摘され続けている「Railsはスケールアップできない」という主張について触れたいと思います。「Railsでは大量のトラフィックを捌くことができない」という主張です。この主張には、一部間違った認識があります。それは、Webサイトをスケールさせるのはあなたが行わなければいけない作業であって、フレームワークがWebサイトをスケールさせるのではない、ということです。Railsは素晴らしいフレームワークではありますが、やはりただのフレームワークでしかありません。正しい問いかけは「Railsで構築されたWebサイトをスケールできるか?」です。この問いかけに対する回答は現時点で明白です。なぜなら、世界で最もトラフィックの大きいWebサイトのいくつかはRailsで構築されており、実績が示されているからです。ただし、Webサイトをスケールさせることは、Railsだけでできることではなく、Rails以外の多くの知識も必要になります。HuluやYellow Pagesなどの大量のトラフィックを日々捌いているWebアプリケーションもRailsで構築されているので、そこまで心配する必要はありません。少なくとも、あなたのアプリケーションがHuluやYellow Pages並みに成長するまでの間は、「Railsだからスケールできない」ということにはならないでしょう。

1.1.3 この本における取り決め

本書で使用している取り決めや表記はわかりやすいものなので、ほとんどは説明不要でしょう。一部の取り決めについては説明が必要かもしれないので本項で補足します。

本書の文中には内部リンク (1.2など) と外部リンク (Ruby on Railsダウンロードページなど) が多数含まれています5。これはHTML版PDF版いずれの場合も同じです。

本書に登場する例の多くは、コマンドライン (ターミナル) のコマンドを使っています。簡素化のため、次のようなUnixスタイルのプロンプト (行の頭に '$' を表示するスタイル) を使用して、その例がコマンドラインであることを示しています。

$ echo "hello, world"
"hello world"

なお、Windows のコマンドラインでは、$の代わりに大なり記号 (>) がプロンプトに表示されるので、$ を > に置き換えて読んでください。

C:\Sites> echo "hello, world"
"hello world"

また、Unixシステム上では、いくつかのコマンドを入力する際にsudoコマンド (他のユーザーの権限でプログラムを実行するためのコマンド) を使わなければならない場合があります6sudoコマンドを使って実行されたプログラムは、通常のユーザーではアクセスできないファイルやディレクトリにアクセスできる「管理者権限」で実行されます。たとえば、1.2.2では、次のような管理者権限で実行するコマンドが登場します。

$ sudo ruby setup.rb

sudo の実行は、ほとんどのUnix/Linux/OS X のシステムで必要です。ただし、1.2.2.3で推奨されているRuby Version Manager (RVM) を使っている場合は、次の例のように管理者権限を使わずに実行できます。

$ ruby setup.rb

Railsには、コマンドラインで実行できるコマンドが多数付属しています。たとえば、1.2.5で使われる以下のコマンドは、開発用WebサーバーをローカルPC上で実行するコマンドです。

$ rails server

Railsチュートリアルにおけるディレクトリの区切りは、コマンドラインのプロンプトと同様にUnixスタイルのスラッシュ(/)を使っています。サンプルアプリケーションの場所は、たとえば次のように表記されます。

/Users/mhartl/rails_projects/sample_app

なお、Windowsのコマンドプロンプトでは、ディレクトリの区切りは次のようにバックスラッシュまたは円マークで表示されます。

C:\Sites\sample_app

Railsで作成するWebアプリケーションが置かれているディレクトリのルート (root) を、Railsルートと呼ぶことにします。この用語は誤解されやすいようで、多くの人がRailsルートを「Railsというアプリケーションそのものがインストールされているディレクトリのルート」だと勘違いします。この点を明確にするため、Railsチュートリアルでは、「Railsルート」については(Railsで作成した)Webアプリケーションのルートディレクトリを指すものとします。また、すべてのディレクトリは、このルートディレクトリを起点とした相対パスで示します。たとえば、著者のサンプルアプリケーションの config ディレクトリは、次の場所にあります。

/Users/mhartl/rails_projects/sample_app/config

このとき、このRailsアプリケーションのルートディレクトリは、configディレクトリの1つ上の階層にあります。したがって、以下の場所がルートディレクトリになります。

/Users/mhartl/rails_projects/sample_app

長いパスを毎回表記すると読みづらくなるので、本書では以下のようなディレクトリパスを参照する場合、

/Users/mhartl/rails_projects/sample_app/config/routes.rb

アプリケーションのルートパスを省略して、単純にconfig/routes.rbと表記することにします。

Railsチュートリアルでは、様々なプログラムの出力結果 (シェルコマンド、バージョン管理ツール、Rubyプログラムの出力結果など) をこまめに記載するようにしています。出力結果は、コンピュータシステムによって微妙に異なるので、本書の出力結果と実際の出力が正確に一致するとは限りません。しかし、細かい出力結果の差異が問題になることはないので、あまり気にする必要はありません。

コンピュータシステムによっては、コマンドを実行した時にエラーが発生するかもしれません。しかし本書では、あらゆる場面を想定してエラー対策をもれなく記載するような不毛な作業は行っていません。そのような場合は、即座にエラーメッセージをGoogleで検索してください。エラーメッセージをGoogleで検索することは、実際のソフトウェア開発でも使われている基本的なテクニックなので、よい練習にもなります。本書のチュートリアルを進めていくうちに何か問題が生じたら、RailsチュートリアルのHelpページを読んでみてください。このページには問題を解決するためのリソースやヒントが記載されているので、問題を解決する手掛かりになるはずです7

1.2 さっそく動作させる

本書の第1章は、さしずめロースクールで言うところの「淘汰期間」(訳注: そこで生き残れるかどうかを選別する期間) のようなものである。本章で開発環境の構築に成功できた人ならば、最後までやり通すことは難しくありません。
—Bob Cavezza (Railsチュートリアル読者)

それでは、Ruby on Railsの開発環境を構築し、最初のアプリを作成してみましょう。プログラミング経験のない方は多少苦労することもあると思いますが、どうか諦めずに頑張ってください。苦労するのは自分だけではありません。これはどんな開発者でも幾度となく通過する道です。ここでの苦労はいつか必ず報われることは、著者が保証いたします。

1.2.1開発環境

開発環境はRailsプログラマ一人ひとりすべて異なります。開発者は慣れてくるに従い、自分の環境を徹底的にカスタマイズするものだからです。開発環境を大別すると、テキストエディタやコマンドラインを使う環境と、IDE (統合開発環境) の2つに分けられます。まずは後者から見てみましょう。

IDE

Rails 用IDEとして最も有名なのはRadRailsRubyMineです。中でもRubyMineが良いという評判をよく聞きます。本書の読者の一人 (David Loeffler) は、RailsチュートリアルでRubyMineを使用する方法8 というドキュメントまで作ってくれました。IDEでの開発を好む方は、上のリンクをチェックしてみて、自分のスタイルに合うかどうか試してみるとよいでしょう。

テキストエディタとコマンドライン

著者自身は、すべてが整った強力なIDEで作業するよりも、「テキストエディタでテキストを編集し、コマンドラインでコマンドを実行する」というシンプルな手段が好みです (図1.1)。ツールの組み合わせは各人の好みやプラットフォームによって変わってきますが、著者がお勧めするツールを以下にご紹介します。

  • テキストエディタ: 断然Sublime Text 2をお勧めします。これは非常によくできたクロスプラットフォームのテキストエディタです。現時点ではまだベータ版であるにもかかわらず、既にベータ版とは思えないほどパワフルなエディタです9。Sublime TextはTextMateの影響を強く受けています。実際、スニペットやカラースキームなどの設定はTextMateと互換性があります。(TextMateはMac OS X版しかありませんが、MacユーザーにとってはTextMateは現在でも良い選択であると言えます。) 次にお勧めするテキストエディタはVim10です。Vimは多くのプラットフォーム上で動作します。Vimは無償で入手できますが、Sublime Textは有償です (訳注: ただし無料の試用期間に制限はありません)。どちらもプロの開発者が使っているエディタですが、著者の経験では、Sublime Text の方が初心者にとってはるかに扱いやすいエディタであると思います。
  • ターミナル: OS Xの場合、iTermか、OSに最初からインストールされているTerminal.appをお勧めします。Linux の場合、デフォルトのターミナルで問題ありません。Windowsの場合、Linuxが動作する仮想マシンを用意して、その仮想マシン上でRailsアプリケーションを開発することが好まれています。この場合、お勧めのコマンドラインは上記と変わりません。Windows自身にRails開発環境をインストールしたい場合、Railsインストーラ付属のコマンドプロンプトを使うことをお勧めします (1.2.2.1)。

なお、Sublime Textを選んだ方のために、より高度な設定方法を解説した「Railsチュートリアル: Sublime Text11」を用意しました。ただしエディタのカスタマイズは何かと厄介でエラーも起きやすいので、この解説はあくまで上級者向けです。Sublime Textは、デフォルト設定のままでもRailsアプリケーションのエディタとして十分に機能します。

editor_shell
図1.1 テキストエディタ/コマンドライン開発環境 (TextMate/ ITERM) (拡大)

ブラウザ

Webブラウザの種類は豊富ですが、多くのRails 開発者はFirefox、Safari、Chromeを使っています。これらの3つのブラウザには、ページの任意の部分を右クリック (またはControlキーを押しながらクリック) して、要素を表示する機能 (Inspect element機能) が標準で組み込まれています。

使用するツールについて

開発環境をあれこれ整え始めると、「すべてのツールを自在に使いこなすにはかなり長い時間がかかりそうだ」と実感することでしょう。実際、テキストエディタやIDEを使いこなせるようになるまでには相当な時間が必要になります。たとえばSublime TextやVimにも専用のチュートリアルがあり、このようなチュートリアルを読み終えるだけでも数週間かかります。開発の経験がない方にとっては相当な驚きだと思いますが、仕事の道具である開発ツールの学習に時間をかけることはいたって普通の事であり、開発者であれば誰もが通る道です。「せっかく良いアイディアが浮かんだから、とにかくRailsでWebアプリをさっと作ってみたいだけなのに、奇妙で古くさい Unix のエディタの使い方を覚えるだけで1週間もかかってる!」とイライラする事もあるでしょう。しかしながら、道具の使い方を覚えることが職人にとって当然の心得であるように、使用するツールについて学ぶことは大切なことです。払った努力に見合う見返りは必ずあります。

1.2.2Ruby、RubyGems、Rails、Git

実質的に世界中のあらゆるソフトウェアは、壊れているか使いにくいかのどちらかだ。人々がソフトウェアに恐怖心を抱くのは、結局これが原因なのだ。人々は何かインストールしようとしたりオンラインフォームに記入したりするたびに、それらがさっぱり動かないという事態にすっかり慣らされてしまっている。正直、私は何かをインストールすることが怖い。これでも私はコンピュータサイエンスの博士号を持っているのだが。
—「Founders at Work」(Jessica Livingston著) のPaul Grahamの言葉。

それでは、RubyとRailsをインストールしましょう。本書では可能な限り多くの環境をカバーするようにしていますが、システムが変われば手順がうまくいかないこともあります。問題が生じた場合は、エラーメッセージをGoogleで検索するか、RailsチュートリアルのHelpページを参照してください。

なお、特に断りのない限り、Railsを含むすべてのソフトウェアはチュートリアルで使用されているものと同じバージョンを使用してください。そうでないと同じ結果を得られないことがあります。 バージョンが少々異なっていても同じ結果を得られることもありますが、特にRailsのバージョンに関しては必ず指定を守ってください。なお、Ruby自身のバージョンは例外的にバージョンにそれほど厳しくありません。本チュートリアル内では、Ruby 1.9.3とRuby 2.0.0は実質的に同じように動作するため、どちらを使ってもチュートリアルには影響しません。お好きな方をご使用ください。(訳注: VagrantやVirtual Boxを使って開発環境を構築するRailsチュートリアル・スターターキットもあります。VagrantやVirtual Boxのような技術に慣れている必要がありますが、環境構築に困ったときはこちらもご検討ください。)

Railsインストーラ (Windows)

最近までWindows上でのRailsのインストールは困難だったのですが、Engine Yardのメンバー (特にDr. Nic WilliamsとWayne E. Seguin) のおかげでインストールが非常に簡単になりました。Windowsユーザーの方はRailsインストーラからインストーラをダウンロードしてください。exeファイルをダブルクリックし、指示に従ってGit、Ruby、RubyGems、そしてRails自身をインストールします (1.2.2.21.2.2.31.2.2.41.2.2.5はスキップしてください)。インストール完了後、1.2.3までスキップして最初のアプリケーション作成を開始してください。

注意: このRailsインストーラによってインストールされるRailsのバージョンは、1.2.2.5に記載のものと異なることがあります。その場合は互換性の問題が生じることもありえます。この問題を解決するために、Railsのバージョン番号順に並んだRubyインストーラのリストを作成してもらうようNicとWayneに働きかけています。

Gitのインストール

Rails環境の多くの部分は、Gitというバージョン管理システムの機能に依存します (Gitの詳細については1.3で解説しています)。Gitは本書で多用されているので、早めにインストールを済ませておきます。Pro Gitの「Gitのインストール」でプラットフォーム毎の解説を行っているので参考にしてください。

Rubyのインストール

次にRubyをインストールします (Rubyのインストールは面倒が多く、エラーもよく発生します。正直、新しいバージョンのRubyをインストールするのが怖いのですが、仕事を進める上では避けて通れません)。

場合によっては、既に自分のシステムにRubyがインストール済みであることもありえます。以下を実行して、

$ ruby -v

バージョン情報を確認してください。Rails 4ではRuby 1.9以降が必須であり、Ruby 2.0が最適です (Rails 4はRuby 1.8.7では動作しません)。本書では、Ruby 1.9.3または2.0.0を前提としますが、Ruby 1.9.2でも動作するはずです。

OS X、またはLinuxを使う場合、Ruby Version Manager (RVM) またはrbenvを使用してRubyをインストールすることを強くお勧めします。RVMを使うと、複数のRubyバージョンを共存させられるのでとても便利です (Windows上で動作する同様のソフトにPikがあります)。同じコンピュータ上で異なるバージョンのRubyやRailsを使い分けたい場合、これらのツールは特に重要です。残念ながら、RVMとrbenvは共存できませんので注意してください。著者は本書でRVMを使い続けているので、今回もRVMを使用します。rbenvもかなり評判がよいようなので、rbenvに慣れ親しんでいるか、rbenvに詳しい人が身近にいるのであればそちらをお使いいただいてもかまいません (訳注: 最近ではrbenvも有力です。rbenvのセットアップ方法については、こちらをご参照ください: rbenvを使ったRailsチュートリアルの進め方)。

Mac OS Xユーザーの場合、Xcodeという開発ツールのインストールが必要なことがあります。Xcodeはサイズが大きいので、それよりもずっとサイズの小さいCommand Line Tools for Xcodeをお勧めします12

Rubyをインストールする前に、以下を実行してRVMをインストールします。

$ curl -L https://get.rvm.io | bash -s

(RVMがインストール済みの場合、以下を実行して

$ rvm get stable

最新のRVMを使用するようにしてください)。

RVMをインストール後、以下を実行してRubyインストールのための要件を調べます。

$ rvm requirements

著者の場合、以下をインストールする必要がありました (ここではHomebrewというMac OS X用のパッケージ管理システムを使用しています)。

$ brew install libtool libxslt libksba openssl

Linuxの場合、apt-getまたはyumを使用して同様にインストールできます。

著者の場合、YAMLライブラリのインストールも必要でした。

# MacでHomebrewを使用している場合
$ brew install libyaml

# DebianベースのLinuxシステムの場合
$ apt-get install libyaml-dev

# Fedora/CentOS/RHEL Linuxシステムの場合
$ yum install libyaml-devel

最後に以下を実行してRuby 2.0.0をインストールします。ここではOpenSSLの場所をRVMに知らせるオプションを追加してあります。

$ rvm install 2.0.0 --with-openssl-dir=$HOME/.rvm/usr
<しばらく待つ>

少なくとも著者の環境では、以上の手順でRuby 2.0.0のインストールに成功しました。

残念ながら、RubyやRVMのインストール中に問題が生じることがよくあります。本書ではなるべく問題が生じにくいような手順を記載しましたが、すべてをカバーすることはできません。問題が生じたらネットを検索するのが早道です。

Rubyをインストールしたら、Railsのアプリケーションを実行するために必要な他のソフトウェア向けにシステムを構成する必要があります。通常、これはgemのインストールに関連します。gemとは自己完結型のRubyコードのパッケージです。バージョン番号の異なるgem同士がコンフリクトすることがあるため、一連のgemを自己完結的にまとめたgemsetというものを作成してバージョンを使い分けるのが便利です。本書向けに、以下を実行してrailstutorial_rails_4_0という名前のgemsetを作成しておくことをお勧めします。

$ rvm use 2.0.0@railstutorial_rails_4_0 --create --default
Using /Users/mhartl/.rvm/gems/ruby-2.0.0-p0 
with gemset railstutorial_rails_4_0

上のコマンドを実行すると、Ruby 2.0.0に関連付けられたrailstutorial_rails_4_0というgemsetを作成し (--create)、その場でgemsetを有効にし (use)、gemsetをデフォルトに設定 (--default) します。これにより、ターミナルウィンドウを開いたときに2.0.0@railstutorial_rails_4_0というRubyとgemsetの組み合わせが常に選択されるようになります。RVMにはgemset管理のためのコマンドが多数用意されています。詳細についてはhttps://rvm.io/gemsetsを参照してください。RVMがうまく動かない場合は以下を実行してヘルプを表示してください。

$ rvm --help
$ rvm gemset --help

RVMの詳細については、「Rails初心者のためのRuby Version Manager (RVM) の概要 (英語)」という記事を参考にしてください13

RubyGemsのインストール

RubyGemsはRubyのプロジェクトのためのパッケージマネージャであり、Rubyのパッケージ (gem) として利用できる多くの有用なライブラリがあります。Railsもgemとしてインストールします。Rubyがインストールされていれば、RubyGemsは簡単にインストールできます。RVMをインストールしてあれば、既にRubyGemsも同時にインストールされているはずです。

$ which gem
/Users/mhartl/.rvm/rubies/ruby-2.0.0-p0/bin/gem

RubyGemsがインストールされていない場合は、RubyGemsをダウンロードして解凍し、作成されたrubygemsディレクトリでセットアッププログラムを実行してください。

$ ruby setup.rb

(アクセス権の問題が生じたら、1.1.3を参照してください。おそらくsudoコマンドを併用する必要があります。)

既にRubyGemsがインストールされている場合は、システムをチュートリアルで使われているバージョンに更新してください。

$ gem update --system 2.0.3

システムを特定のバージョンに固定しておけば、今後RubyGemsが変更されたときのコンフリクトを防止できます。

gemをインストールすると、RubyGemsによってriとrdocという2種類のドキュメントがデフォルトで作成されます。多くのRubyやRailsの開発者はこれらのドキュメントが自動生成される時間すら惜しいと考えます (ほとんどのプログラマーはネイティブのriやrdocなど参照せず、さっさとオンラインドキュメントを見に行ってしまいます)。riやrdocの自動生成を抑制するには、.gemrcというファイルをリスト1.1のようにホームディレクトリに作成し、そこにリスト1.2の内容を追加しておきます。(チルダ“~”は「ホームディレクトリ」を表します。 .gemrcファイルのようにファイル名冒頭にドット「.」を付けると隠しファイルとして扱われます。Unixの設定ファイルにはこのような名前を付けるのが習わしとなっています。)

リスト1.1 gem設定ファイルを作成する。
$ subl ~/.gemrc

冒頭のsublはOS XでSublime Textを起動するコマンドです。設定方法については「OS X コマンドライン用Sublime Text 2ドキュメント (英語)」を参照してください。使用しているプラットフォームやエディタが異なる場合、sublを他のエディタコマンドに読み替えてください (エディタのアイコンをダブルクリックするか、matevimgvimmvimなどのコマンドに差し替えます)。記述を簡素化するため、本書で以後sublと書かれている箇所はお好みのエディタに読み替えてください。

リスト1.2 .gemrcにriとrdoc生成を抑制するコマンドを追加する。
install: --no-rdoc --no-ri
update:  --no-rdoc --no-ri

Railsのインストール

RubyGemsをインストールしてしまえば、Railsのインストールは簡単です。本書ではRails 4.0を使用します。以下を実行してインストールしてください。

$ gem install rails --version 4.0.5

正しくインストールされたかどうかを確認するには、以下のコマンドを実行してバージョンを確認してください。

$ rails -v
Rails 4.0.5

注: 1.2.2.1でRailsインストーラを使用してRailsをインストールした場合、Railsのバージョンが上と異なっている可能性があります。この記事の執筆時点では、このバージョンの違いは影響していません。ただし、今後Railsのバージョンが本書指定のものから離れていくにつれ、バージョンの違いによる影響が顕著になる可能性があります。特定のバージョンのRailsインストーラへのリンクを作成してもらえるよう、現在Engine Yardに働きかけています。

Linuxを実行している場合は、この時点で、他にもいくつかのパッケージをインストールする必要が生じる場合があります。

$ sudo apt-get install libxslt-dev libxml2-dev libsqlite3-dev # Linuxのみ

1.2.3最初のアプリケーション

どんなRailsアプリケーションも最初の作成手順は同じです。rails newコマンドを実行して作成します。このコマンドを実行するだけで、指定のディレクトリにRailsアプリケーションのスケルトンを簡単に作成できます。これを行うには、まず複数のRailsプロジェクトを保存するためのディレクトリを作成し、rails newを実行して最初のアプリケーションを作成します (リスト1.3)。

(訳注: 下記のリストを実行する前に、rails --versionでRails 4.0.5が入っていることを確認してください。もしRails 4.1やRails 4.2などのバージョンが入ってしまっている場合は、gem install rails --version 4.0.5というコマンドを実行してRails 4.0.5をインストールしてください。Rails 4.0.5 がインストールされている状態で、rails _4.0.5_ new first_appと実行すると、Rails 4.0.5を使って新規プロジェクトを作成することができます。詳しくは @jnchito さんの記事「Railsチュートリアルでrails newするときはバージョンを指定しよう」をご参照ください。)

リスト1.3 rails newを実行してアプリケーションを新規作成する。
$ mkdir rails_projects
$ cd rails_projects
$ rails new first_app
      create  
      create  README.rdoc
      create  Rakefile
      create  config.ru
      create  .gitignore
      create  Gemfile
      create  app
      create  app/assets/javascripts/application.js
      create  app/assets/stylesheets/application.css
      create  app/controllers/application_controller.rb
      .
      .
      .
      create  test/test_helper.rb
      create  tmp/cache
      create  tmp/cache/assets
      create  vendor/assets/javascripts
      create  vendor/assets/javascripts/.keep
      create  vendor/assets/stylesheets
      create  vendor/assets/stylesheets/.keep
         run  bundle install
.
.
.
Your bundle is complete!Use `bundle show [gemname]` 
to see where a bundled gem is installed.

リスト1.3の下の方に注目してください。rails newを実行すると、ファイルの作成後にbundle installコマンドが自動的に実行されています。もしbundle installが実行されていなくても心配はいりません。落ち着いて1.2.4の手順に従えば実行できます。

railsコマンドによって非常に多くのファイルとディレクトリが作成されていることに注目してください。ディレクトリとファイルの構造 (図1.2) がこのように標準化されていることは数あるRailsの利点の1つであり、何もない状態から実際に動かせるアプリケーションをただちに作成することができます。さらに、この構造はすべてのRailsアプリに共通しているので、他の開発者の書いたコードの挙動を容易に推察できます。Railsがデフォルトで作成するファイルについては表1.1を参照してください。これらのファイルやディレクトリの目的については本書全体に渡って説明いたします。特に、5.2.1以降ではRails 3.1の新機能であるAsset Pipelineの一部となるapp/assetsディレクトリについて詳しく説明します。Asset Pipelineによって、CSS (Cascading Style Sheet) やJavaScriptファイルなどのアセットを簡単に編成したりデプロイすることができます。

directory_structure_rails_4_0
図1.2新規作成されたRailsアプリケーションのディレクトリ構造。(拡大)
ファイル/ディレクトリ目的
app/モデル、ビュー、コントローラ、ヘルパーなどを含む主要なアプリケーションコード
app/assetsアプリケーションで使用するCSS (Cascading Style Sheet)、JavaScriptファイル、画像などのアセット
bin/バイナリ実行可能ファイル
config/アプリケーションの設定
db/データベース関連のファイル
doc/マニュアルなど、アプリケーションのドキュメント
lib/ライブラリモジュール
lib/assetsライブラリで使用するCSS (Cascading Style Sheet)、JavaScriptファイル、画像などのアセット
log/アプリケーションのログファイル
public/エラーページなど、一般(Webブラウザなど)に直接公開するデータ
bin/railsコード生成、コンソールの起動、ローカルのWebサーバーの立ち上げなどに使用するRailsスクリプト
test/アプリケーションのテスト (3.1.2で作成するspec/ディレクトリがあるため、現在は使用されていません)
tmp/一時ファイル
vendor/サードパーティのプラグインやgemなど
vendor/assetsサードパーティのプラグインやgemで使用するCSS (Cascading Style Sheet)、JavaScriptファイル、画像などのアセット
README.rdocアプリケーションの簡単な説明
Rakefilerakeコマンドで使用可能なタスク
Gemfileこのアプリケーションに必要なGemの定義ファイル
Gemfile.lockアプリケーションのすべてのコピーが同じgemのバージョンを使用していることを確認するために使用されるgemのリスト
config.ruRackミドルウェア用の設定ファイル
.gitignoreGitに無視させたいファイルを指定するためのパターン
表1.1: デフォルトのRailsフォルダ構造まとめ。

1.2.4 Bundler

Railsアプリケーションを新規作成したら、次はBundlerを実行して、アプリケーションに必要なgemをインストールおよびインクルードします。1.2.3でも簡単に説明したように、Bundlerはrailsによって自動的に実行 (この場合はbundle install) されます。ここではデフォルトのアプリケーションgemを変更してBundlerを再度実行してみます。そのためにまず、好みのエディタでGemfileを開きます。

$ cd first_app/
$ subl Gemfile

ファイルの内容はリスト1.4のようになっているはずです。Gemfileの内容はRubyのコードですが、ここでは文法を気にする必要はありません。Rubyの詳細については第4章で説明します。

リスト1.4 first_appディレクトリ直下にあるデフォルトのGemfile
source 'https://rubygems.org'

# Use sqlite3 as the database for Active Record
gem 'sqlite3'

# Use SCSS for stylesheets
gem 'sass-rails', '~> 4.0.2'

# Use Uglifier as compressor for JavaScript assets
gem 'uglifier', '>= 1.3.0'

# Use CoffeeScript for .js.coffee assets and views
gem 'coffee-rails', '~> 4.0.1'

# See https://github.com/sstephenson/execjs#readme 
# for more supported runtimes
# gem 'therubyracer', platforms: :ruby

# Use jquery as the JavaScript library
gem 'jquery-rails'

# Turbolinks makes following links in your web application faster.
# Read more: https://github.com/rails/turbolinks
gem 'turbolinks'

# Build JSON APIs with ease. 
# Read more: https://github.com/rails/jbuilder
gem 'jbuilder', '~> 1.0.1'

group :doc do
  # bundle exec rake doc:rails generates the API under doc/api.
  gem 'sdoc', require: false
end

# Use ActiveModel has_secure_password
# gem 'bcrypt-ruby', '~> 3.1.2'

# Use unicorn as the app server
# gem 'unicorn'

# Use Capistrano for deployment
# gem 'capistrano', group: :development

# Use debugger
# gem 'debugger', group: [:development, :test]

ほとんどの行はハッシュシンボル #でコメントされています。これらの行では、よく使われているgemとBundlerの文法の例をコメント形式で紹介しています。この時点では、デフォルト以外のgemをインストールする必要はありません。

gemコマンドで特定のバージョン番号を指定しない限り、Bundlerは自動的に最新バージョンのgemを取得してインストールしてくれます。残念ながらgemを更新すると小さな問題を起こすことがよくあるので、リスト1.4からコメントアウト行を除いたリスト1.5に示したように、このチュートリアルではたいていの場合動作確認済みのバージョン番号を指定しています。

リスト1.5 Ruby gemのバージョンを指定したGemfile
source 'https://rubygems.org'
ruby '2.0.0'
#ruby-gemset=railstutorial_rails_4_0

gem 'rails', '4.0.5'

group :development do
  gem 'sqlite3', '1.3.8'
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

リスト1.5には以下の行が追加されています。

ruby '2.0.0'
#ruby-gemset=railstutorial_rails_4_0

上の行は、アプリケーションが前提とするRubyのバージョンを指定しています。この指定は、特定のRVM gemset (1.2.2.3) に合わせてアプリケーションをデプロイする (1.4) ときに特に便利です。上の行のうち、#で始まる行はRubyのコメントです。RVMを使用しない場合はこのコメント行は無視されますが、RVMを使用している場合はこの行で指定しているgemsetが使用されます (2.0.0以外のバージョンのRubyを使用する場合は、Rubyのバージョンを書き換えてください)。

更新されたGemfileでは、JQueryに関する以下の行も変更します。JQueryはRailsのデフォルトのJavaScriptライブラリです。

gem 'jquery-rails'

上を以下のように変更します。

gem 'jquery-rails', '3.0.4'

他にも変更を行います。

gem 'sqlite3'

上を以下のように変更します。

group :development do
  gem 'sqlite3', '1.3.8'
end

この変更を行うと、Bundlerはsqlite3 gemの1.3.8を強制的にインストールします。ここではさらに、SQLiteをdevelopment環境 (7.1.1) でのみ使用するための指定も行なっていることに注目してください。こうすることで、Heroku (1.4) で使用するデータベースソフトウェアと衝突する可能性を避けられます。

リスト1.5では他にもいくつかの変更を行なっています。

# Use SCSS for stylesheets
gem 'sass-rails', '~> 4.0.2'

# Use Uglifier as compressor for JavaScript assets
gem 'uglifier', '>= 1.3.0'

# Use CoffeeScript for .js.coffee assets and views
gem 'coffee-rails', '~> 4.0.1'

上を以下のように変更します。

gem 'sass-rails',   '4.0.5'
gem 'uglifier', '2.1.1'
gem 'coffee-rails', '4.0.1'

以下の構文を実行すると

gem 'uglifier', '>= 1.3.0'

uglifierのバージョンが1.3.0以上であれば最新バージョンのgemがインストールされます。 極端に言えばバージョンが7.2であってもそれが最新ならインストールされます。なお、uglifierはAsset Pipelineでファイル圧縮を行うためのものです。一方、例えば、以下のコードを実行すると

gem 'coffee-rails', '~> 4.0.0'

coffee-rails (これもAsset Pipelineで使用するgemです) のバージョンが4.0.0より大きく、4.1より小さい場合にインストールするようになります。つまり、>=と書くとbundle installの実行時に必ずアップグレードが行われますが、~> 4.0.0と書くとマイナーなアップグレードしか行われないことになります。この場合4.0.0から4.0.1へのアップグレードは行われますが、4.0から4.1へのアップグレードは行われません。経験上、残念ながらマイナーアップグレードですら問題を引き起こすことがあります。このため、Railsチュートリアルでは基本的にすべてのgemでバージョンをピンポイントで指定しています。ベテラン開発者であれば、Gemfile~>が指定されているgemも含め、基本的に最新のgemを使用しても構いません。ただし、それによって本書に記載されているのと異なる挙動を示す可能性がありますので、その場合は各自でご対応願います。

Gemfileを正しく設定した後、bundle update14bundle installを使用してgemをインストールします。

$ bundle update
$ bundle install
Fetching source index for https://rubygems.org/
.
.
.

bundle installコマンドの実行にはしばらく時間がかかるかもしれません。完了後、アプリケーションが実行可能になります。

1.2.5 rails server

1.2.3rails newコマンドと1.2.4bundle installコマンドを実行したことにより、実際に動かすことのできるアプリケーションが作成されました。うれしいことに、Railsには開発マシンでのみブラウズできるローカルWebサーバーを起動するためのコマンドラインプログラム (スクリプト) が付属しているので、以下のコマンドを実行するだけで簡単に起動することができます。

$ rails server
=> Booting WEBrick
=> Rails application starting on http://0.0.0.0:3000
=> Call with -d to detach
=> Ctrl-C to shutdown server

(JavaScriptランタイムがインストールされていないというエラーが表示された場合は、GitHubのexecjsページにあるインストール可能なランタイムを確認してください。Node.jsが特にお勧めです)。表示されたメッセージは、RailsアプリケーションがIPアドレス0.0.0.0ポート番号300015で動作していることを示しています。このIPアドレスは、このマシンに設定されているすべてのIPアドレスで受信待ち (listen) するように指定しています。これにより、127.0.0.1(localhost)という特別なアドレスでアプリケーションをブラウズできます。図1.3は、http://localhost:3000/をブラウザで表示した時の結果です。

riding_rails_4_0
図1.3 デフォルトのRailsページ。(拡大)

この最初のアプリケーションの情報を見るには、「About your application’s environment」のリンクをクリックしてください。図1.4のような画面が表示されます (図1.4は著者の環境でキャプチャしたものなので、実際には若干異なる可能性があります)。

riding_rails_environment_4_0
図1.4 アプリケーション環境が表示されているデフォルトページ。(拡大)

もちろん、いずれデフォルトのRailsページは不要になりますが、アプリケーションが動いているのを見るのは気分のいいものです。5.3.2ではこのデフォルトページを削除し、カスタマイズしたホームページに置き換える予定です。

1.2.6 Model-View-Controller (MVC)

まだ始まったばかりですが、今のうちにRailsアプリケーションの全体的な仕組みを知っておくことは後々役立ちます (図1.5)。デフォルトのRailsアプリ構造 (図1.2) を眺めてみると、app/というディレクトリがあり、その中に「models」「views」「controllers」という3つのサブディレクトリがあることに気付いた方もいると思います。ここにはRailsがMVC (model-view-controller) というアーキテクチャパターンを採用していることが暗に示されています。MVCでは、ドメインロジック (ビジネスロジックともいいます) と、グラフィカルユーザーインターフェイス (GUI) と密に関連する入力/表示ロジックを分離します。Webアプリケーションの場合、「ドメインロジック」はユーザーや記事、商品などのデータモデルに相当し、ユーザーインターフェイスはWebページを指します。

Railsアプリと通信する際、ブラウザは一般的にWebサーバーにrequest (リクエスト) を送信し、これはリクエストを処理する役割を担っているRailsのcontroller (コントローラ) に渡されます。 コントローラは、場合によってはすぐにview (ビュー) を生成してHTMLをブラウザに送り返します。動的なサイトでは、一般にコントローラは (ユーザーなどの) サイトの要素を表しており、データベースとの通信を担当しているRubyのオブジェクトであるmodel (モデル) と対話します。モデルを呼び出した後、コントローラは、ビューをレンダリングし、完成したWebページをHTMLとしてブラウザに返します。

mvc_schematic
図1.5: model-view-controller (MVC) アーキテクチャの概念図。

今はまだこの解説が少し抽象的に思えるかもしれませんが、この章は後に何度も参照する事になるのでご安心ください。MVCについては、2.2.2のデモアプリケーションの説明の中でもさらに詳しく解説します。サンプルアプリケーションでは、MVCの3つの要素をすべて使用します。3.1.2ではまずコントローラとビューを使用し、モデルは6.1から使い始めます。 7.1.2では3つの要素が協調して動作します。

1.3 Gitによるバージョン管理

新しく動作するRailsアプリが完成したところで、さっそくアプリケーションのソースコードをバージョン管理下に置きましょう。これを行わないとアプリケーションが動かないということではありませんが、ほとんどのRails開発者はバージョン管理は開発現場において必要不可欠であると考えています。バージョン管理システムを導入しておけば、プロジェクトのコードの履歴を追ったり、うっかり削除してしまったファイルを復旧 (ロールバック) したりという作業が行えるようになります。バージョン管理システムを熟知することは、今やあらゆるソフトウェア開発者にとって必須のスキルであると言ってよいでしょう。

バージョン管理システムにもさまざまなものがありますが、RailsコミュニティではLinuxカーネル用にLinus Torvaldsにより開発された分散バージョン管理システムであるGitが主流になっています。Git (というよりバージョン管理) はそれだけで大きなテーマなので、すべてを説明しようとすると軽く一冊の本を超えてしまいます。本書では簡単に言及するにとどめますが、幸いネット上には無償で利用できるリソースがあふれています。その中でも特に「Pro Git」Scott Chacon (Apress, 2009) をお勧めいたします (訳注: Pro Gitには素晴らしい日本語版があります: http://git-scm.com/book/ja/ )。ソースコードのバージョン管理は何としても導入してください。バージョン管理はRailsを使用するどんな場面でも必要になりますし、バージョン管理システムを応用して、自分の作成したコードを他の開発者と簡単に共有したり 1.3.4)、最初の章で作成したアプリケーションを本番サーバーへデプロイしたりすることもできる (1.4) からです。

1.3.1インストールとセットアップ

Gitがインストールされていない場合は、まず1.2.2.2に従ってGitをインストールしてください (リンク先の節でも述べているように、Pro Gitの「Gitのインストール」の記載に従うことになります)。

最初のシステムセットアップ

Gitのインストール後、最初に1回だけ行う必要のある設定があります。これはsystemセットアップと呼ばれ、使用するコンピュータ1台につき1回だけ行います。

$ git config --global user.name "あなたの名前"
$ git config --global user.email your.email@example.com

著者の場合、checkout という長ったらしいコマンドの代わりにcoという短いコマンド (エイリアス) も使えるようにしています。これを行うには以下を実行します。

$ git config --global alias.co checkout

本書では、coエイリアスを設定していないシステムでも動作するようにフルスペルのcheckoutを使用していますが、著者自身は実際の開発ではほとんどいつもgit coを使ってプロジェクトをチェックアウトしています。

手順の最後として、Gitのコミットメッセージを入力するときに使用するエディタを設定できます。Sublime Text、TextMate、gVim、MacVimなどのGUIエディタを使用する場合、シェルから離れずシェル内で起動するようフラグを付けてください16

$ git config --global core.editor "subl -w"

"subl -w" の部分は、TextMateの場合は "mate -w"、gVimの場合は"gvim -f"、MacVimの場合は"mvim -f" にそれぞれ置き換えます。

最初のリポジトリセットアップ

今度は、リポジトリを作成するたびに必要な作業を行います。まず、Railsアプリケーションのルートディレクトリに移動し、新しいリポジトリの初期化を行います。

$ git init
Initialized empty Git repository in /Users/mhartl/rails_projects/first_app/.git/

次に、プロジェクトのファイルをリポジトリに追加します。ここで1つ問題があります。Gitはすべてのファイルの変更履歴を管理するようになっていますが、管理対象に含めたくないファイルもあります。たとえば、Railsによって作成されるログファイルは頻繁に内容が変わるので、いちいちバージョン管理に更新させたくありません。Gitにはこういったファイルを管理対象から除外する機能があります。.gitignoreというファイルをアプリケーションのルートディレクトリに置き、除外したいファイルを指定するためのルールをそこに記載します17

表1.1をもう一度見てみると、railsコマンドを実行した時にRailsアプリケーションのルートディレクトリに.gitignoreファイルが作成されています (リスト1.6)。

リスト1.6 railsコマンドで作成されるデフォルトの.gitignoreファイルの内容。
# See http://help.github.com/ignore-files/ for more about ignoring files.
#
# If you find yourself ignoring temporary files generated by your text editor
# or operating system, you probably want to add a global ignore instead:
#   git config --global core.excludesfile '~/.gitignore_global'

# Ignore bundler config.
/.bundle

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

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

リスト1.6の設定では、ログファイル、Railsの一時ファイル (tmp)、SQLiteデータベースなどが除外されます。(log/ディレクトリ以下のログファイルを除外したい場合は、log/*.logと指定することで、ファイル名が.logで終わるファイルが除外されます。これらのファイルは頻繁に更新されるため、バージョン管理に含めるのは何かと不便です。さらに、他の開発者と共同作業を行う場合にこのようなファイルをバージョン管理に含めると無用な衝突 (conflict) が発生し、関係者一同が無用のストレスにさらされることになりかねません。

最初のうちはリスト1.6.gitignore設定でもよいでしょう。しかし、リスト1.7の方がさらに便利で、セキュリティ上も有利 (リスト3.2) なのでお勧めです。この.gitignoreでは、Railsドキュメントファイル、VimやEmacsのスワップファイル、そしてOS Xユーザーにはお馴染みの、あのいまいましい.DS_Storeディレクトリ (MacのFinder操作で作成される隠しディレクトリ) も管理対象から除外されます。この拡張版除外設定を使用したい場合は、.gitignoreを好みのテキストエディタで開き、リスト1.7の内容で置き換えます。

リスト1.7 より多くのパターンを除外する.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

1.3.2追加とコミット

最後に、新しく作成したRailsプロジェクトのファイルをGitに追加し、次にそれをコミットします。ファイルを追加する (.gitignoreで指定されているものを除く) には、以下のコマンドを実行します。

$ git add .

ここで「.」は現在のディレクトリ (カレントディレクトリ) を指します。Gitは再帰的にファイルを追加できるので、自動的にすべてのサブディレクトリも追加されます。このコマンドにより、プロジェクトのファイルは、コミット待ちの変更が格納されている「ステージングエリア」という一種の待機場所に追加されます。ステージングエリアにあるファイルのリストを表示するには、statusコマンドを実行します18

$ git status
# On branch master
#
# Initial commit
#
# Changes to be committed:
#   (use "git rm --cached <file>..." to unstage)
#
#       new file:   README.rdoc
#       new file:   Rakefile
.
.
.

(出力結果が長いので、省略された部分を示すために縦点を使っています。)

変更を保存するには、commitコマンドを使います。

$ git commit -m "Initialize repository"
[master (root-commit) df0a62f] Initialize repository
42 files changed, 8461 insertions(+), 0 deletions(-)
create mode 100644 README.rdoc
create mode 100644 Rakefile
.
.
.

-mフラグは、コミットメッセージ (コミット内容の覚書) をその場で追加する場合に使用します。-mを省略した場合、1.3.1で設定されたエディタが起動され、コミットメッセージの入力を求められます。

ここでコミットについて少し解説しておきます。Gitにおけるコミットは、あくまでローカルマシン上での操作であることに注意してください。この点については、もう一つの有名なオープンソースバージョン管理システムであるSubversionとははっきり異なります。Gitの場合、コミットを実行してもリモート上にあるリポジトリを直接変更することはありません。Gitでは、ローカルでの変更保存 (git commit) と、リモート上のリポジトリへの変更反映 (git push) の2段階に分かれています。1.3.5にはこのpushの例が記載されています。

ちなみに、logコマンドでコミットメッセージの履歴を参照できます。

$ git log
commit df0a62f3f091e53ffa799309b3e32c27b0b38eb4
Author: Michael Hartl <michael@michaelhartl.com>
Date:   Thu Oct 15 11:36:21 2009 -0700

  Initialize repository

git logを終了するにはqキーを押してください。

1.3.3Gitのメリット

今の時点では、ソースコードをバージョン管理下に置かなければならない理由が今ひとつよくわからないという方がいるかもしれませんので、例を1つ紹介します (この後の章でも多くの実例を紹介します)。仮に、あなたが重要なapp/controllers/ディレクトリを削除してしまったとしましょう。

$ ls app/controllers/
application_controller.rb
$ rm -rf app/controllers/
$ ls app/controllers/
ls: app/controllers/: No such file or directory

ここでは、Unixコマンドのlsapp/controllers/ディレクトリの中身を表示した後、rmコマンドをうっかり実行してこのディレクトリを削除してしまいました。-rfフラグは、「recursive」(サブディレクトリやその中のファイルもすべて削除する)、「 force」(削除して良いかどうかをユーザーに確認しない) を指定するオプションです。

現在の状態を確認してみましょう。

$ git status
# On branch master
# Changed but not updated:
#   (use "git add/rm <file>..." to update what will be committed)
#   (use "git checkout -- <file>..." to discard changes in working directory)
#
#       deleted:    app/controllers/application_controller.rb
#
no changes added to commit (use "git add" and/or "git commit -a")

ファイルがいくつか削除されましたが、この変更が行われたのは現在の「作業ツリー」内のみなので、まだコミット (保存) されていません。つまり、以前のコミットをcheckoutコマンド (と、現在までの変更を強制的に上書きして元に戻すための-fフラグ) でチェックアウトすれば、簡単に削除前の状態に戻すことができます。

$ git checkout -f
$ git status
# On branch master
nothing to commit (working directory clean)
$ ls app/controllers/
application_controller.rb

削除されたディレクトリとファイルが無事復旧しました。これでひと安心です。

1.3.4GitHub

Gitを使用してプロジェクトをバージョン管理下に置くことができたので、今度はGitHubにソースコードをアップロードしてみましょう。GitHubは、Gitリポジトリの置き場所を提供したり (ホスティング)、リポジトリを開発者同士で共有するサービスを提供したりするWebサービスとして有名です。リポジトリをGitHubにわざわざプッシュするのには2つの理由があります。1つ目は、ソースコード (とそのすべての変更履歴) の完全なバックアップを作成することです。2つ目は、他の開発者との共同作業をより簡単に行うことです。GitHubへのプッシュは必須ではありませんが、GitHubのメンバーになっておくと、多くのオープンソースプロジェクトに参加できるようになります。

create_first_repository_4_0
図1.6アカウント作成直後のGitHubページ。(拡大)

GitHubにはさまざまな有料プランがありますが、オープンソースのコードなら無料で利用できるので、初めて利用するのであれば無料のGitHubアカウントを作成しましょう (念のため、GitHubのSSHキー作成方法のチュートリアルを先に読んでおいてください)。 アカウント作成後、[Create repository]をクリックし、図1.6に従ってフォームに記入します。(注意: このときにREADMEファイルを使用してリポジトリを初期化しないでくださいrails newコマンドを実行するときにこれらのファイルは自動的に作成されるからです) 。リポジトリを作成したら、以下を実行してアプリケーションをプッシュします。

$ git remote add origin https://github.com/<username>/first_app.git
$ git push -u origin master

最初のコマンドは、現在のメイン (master) ブランチ用の "origin" としてGitHubに追加します。次のコマンドで実際に GitHubにプッシュします (-uフラグについては気にする必要はありません。気になるのであれば "git set upstream"で検索してみてください)。もちろん、<username>はあなたの名前に置き換えてください。たとえば、著者が実行したコマンドは以下のとおりです。

$ git remote add origin https://github.com/mhartl/first_app.git

このコマンドを実行すると、GitHubにファイル閲覧、コミット履歴の表示など多数の機能を備えたアプリケーションのリポジトリ用のページが作成されました (図1.7)。

github_repository_page_4_0
図1.7GitHubのリポジトリページ。(拡大)

なお、GitHubにはコマンドラインインターフェイスを拡張したGUIアプリケーションもあります。 GUIアプリケーションの方が好みであれば、GitHub for WindowsGitHub for Macをチェックしてみてください (GitHub for Linuxは今のところ存在しないようです)。

1.3.5ブランチ (branch)、変更 (edit)、 コミット (commit)、マージ (merge)

1.3.4で紹介した手順に従っていれば、GitHubは自動的にリポジトリのメインページにREADMEファイルの内容を表示していることに気付くでしょう。このチュートリアルでは、プロジェクトはrailsコマンドによって作成されたRailsアプリケーションなので、READMEファイルはRailsに既に含まれています (図1.8)。READMEファイルの拡張子は.rdocになっているので、GitHubでは適切なフォーマットで表示されます。しかしその内容はRailsフレームワークそのものに関するもので、そのままでは役に立ちません。この節ではREADMEの内容を編集し、プロジェクトに関する記述に置き換えます。それと同時に、Gitでbranch、edit、commit、mergeを行う際にお勧めのワークフローの実例をお見せします。

rails_readme_4_0
図1.8GitHub上の、無用な初期のREADMEファイル。(拡大)

ブランチ (branch)

Gitは、ブランチ (branch) を極めて簡単かつ高速に作成することができます。ブランチは基本的にはリポジトリのコピーで、ブランチ上では元のファイルを触らずに新しいコードを書くなど、自由に変更や実験を試すことができます。通常、親リポジトリはマスターブランチと呼ばれ、トピックブランチ (短期間だけ使う一時的なブランチ) はcheckout-bフラグを使って作成できます。

$ git checkout -b modify-README
Switched to a new branch 'modify-README'
$ git branch
master
* modify-README

2つ目のコマンド (git branch) は、単にすべてのローカルブランチを一覧表示しているだけです。「*」はそのブランチが現在使用中であることを表します。1番目のgit checkout -b modify-READMEコマンドで、ブランチの新規作成とそのブランチへの切り替えが同時に行われていることに注目してください。modify-READMEブランチに*が付いていることで、このブランチが現在使用中であることが示されています (1.3coエイリアスを設定した場合は、git co -b modify-READMEと入力することもできます)。

ブランチの真価は他の開発者と共同で作業する時に発揮されますが19、このチュートリアルのように一人で作業する時にも非常に有用です。マスターブランチはトピックブランチで行った変更に影響されないので、たとえブランチ上のコードがめちゃくちゃになってしまっても、マスターブランチをチェックアウトしてトピックブランチを削除すれば、いつでも変更を破棄する事ができます。具体的な方法についてはこの章の最後で説明します。

ちなみに、通常このような小さな変更のためにわざわざブランチを作成する必要はありませんが、ブランチがよい習慣であることに変わりはないので、少しでも練習しておきましょう。

変更 (Edit)

トピックブランチを作成後、READMEの内容をわかりやすく書き換えてみましょう。著者の場合、デフォルトのRDocを編集するときには主にMarkdownというマークアップ言語を使用しています。拡張子を.mdにしておけば、GitHubにアップロードしたときに自動的にドキュメントがきれいに整形されます。今回はGitに付属するmvコマンド (注: Unixのmvコマンドではありません!) を使ってREADMEの拡張子を変更し、それからREADMEの内容をリスト1.8の内容に書き換えます。

$ git mv README.rdoc README.md
$ subl README.md
リスト1.8 新しいREADMEファイル (README.md)。
# Ruby on Rails チュートリアル:サンプルアプリケーション

This is the first application for the
[*Ruby on Rails Tutorial*](http://railstutorial.jp/)
by [Michael Hartl](http://www.michaelhartl.com/).

コミット (commit)

変更が終わったら、ブランチの状態を確認してみましょう。

$ git status
# On branch modify-README
# Changes to be committed:
#   (use "git reset HEAD <file>..." to unstage)
#
#       renamed:    README.rdoc -> README.md
#
# Changed but not updated:
#   (use "git add <file>..." to update what will be committed)
#   (use "git checkout -- <file>..." to discard changes in working directory)
#
#       modified:   README.md
#

この時点で、1.3.2のようにgit add .を実行することもできますが、Gitには現存するすべてのファイル (git mvで作成したファイルも含む) への変更を一括でコミットする-aフラグがあります。このフラグは非常によく使われます。なお、git mvで作成されたファイルは、Gitによって新規ファイルではなく変更として扱われます。

$ git commit -a -m "Improve the README file"
2 files changed, 5 insertions(+), 243 deletions(-)
delete mode 100644 README.rdoc
create mode 100644 README.md

-aフラグは慎重に扱ってください。最後のコミット後に新しいファイルを追加した場合は、まずgit addを実行してバージョン管理下に置く必要があります。

コミットメッセージは現在形で書くようにしましょう。Gitのモデルは、(単一のパッチではなく) 一連のパッチとしてコミットされます。そのため、コミットメッセージを書くときには、そのコミットが「何をしたのか」と履歴スタイルで書くよりも「何をする」ためのものなのかを書く方が、後から見返したときにわかりやすくなります。さらに、現在形で書いておけば、Gitコマンド自身によって生成されるコミットメッセージとも時制が整合します。詳細についてはGitHubに投稿された「最新のコミット方法 (英語)」を参照してください。

マージ (merge)

ファイルの変更が終わったので、マスターブランチにこの変更をマージ (merge) します。

$ git checkout master
Switched to branch 'master'
$ git merge modify-README
Updating 34f06b7..2c92bef
Fast forward
README.rdoc     |  243 --------------------------------------------------
README.md       |    5 +
2 files changed, 5 insertions(+), 243 deletions(-)
delete mode 100644 README.rdoc
create mode 100644 README.md

Gitの出力には34f06b7のような文字列が含まれていることがあります。Gitはこれらをリポジトリの内部処理に使用しています。この文字列は環境の違いにより上記のものと少し異なるかもしれませんが、他の部分はほぼ同じはずです。

変更をマージした後は、git branch -dを実行してトピックブランチを削除すれば終わりです。

$ git branch -d modify-README
Deleted branch modify-README (was 2c92bef).

トピックブランチの削除は必須ではありません。実際、トピックブランチを削除せずにそのままにしておくことはよく行われています。トピックブランチを削除せずに残しておけば、トピックブランチとマスターブランチを交互に行き来して、きりの良い所で変更をマージする事ができます。

上で述べたように、git branch -Dでトピックブランチ上の変更を破棄することもできます。

# これはあくまで例です。ブランチでミスをした時以外は実行しないでください。
$ git checkout -b topic-branch
$ <ブランチをめちゃくちゃにしてみる>
$ git add .
$ git commit -a -m "Major screw up"
$ git checkout master
$ git branch -D topic-branch

-dフラグと異なり、-Dフラグは変更をマージしていなくてもブランチを削除してくれます。

プッシュ (push)

READMEファイルの更新が終わったので、GitHubに変更をプッシュして結果を見てみましょう。既に1.3.4で一度プッシュを行ったので、大抵のシステムではgit pushを実行するときにorigin masterを省略できます。

$ git push

先ほど説明したとおり、Markdownで記述された新しいファイルはGitHubできれいにフォーマットされます (図1.9)。

new_readme_4_0
図1.9 Markdownを使用してフォーマットされた改良版READMEファイル。(拡大)

1.4デプロイする

この段階では空っぽのRailsアプリケーションしかありませんが、本番環境にデプロイしてしまいましょう。アプリケーションのデプロイは必須ではありませんが、頻繁にデプロイすることによって、開発サイクルでの問題を早い段階で見つけることができます。開発環境のテストを繰り返すばかりで、いつまでも本番環境にデプロイしないままだと、アプリケーションを公開する時に思わぬ事態に遭遇する可能性が高まります20

かつてはRailsアプリのデプロイは大変な作業でしたが、ここ数年急速に簡単になってきており、さまざまな本番環境を選択できるようになりました。Phusion Passenger (ApacheやNginx21Webサーバー用のモジュール) を実行できるさまざまな共有ホストや仮想プライベートサーバー (VPS) の他に、フルサービスデプロイメントを提供するEngine YardRails Machine、クラウドサービスを提供するEngine Yard CloudHerokuなどがあります。

著者のお気に入りはHerokuで、Railsを含むRuby Webアプリ用のホスティングプラットフォームです。Herokuは、ソースコードのバージョン管理にGitを使用していれば、Railsアプリケーションを簡単に本番環境にデプロイできます (Gitを導入したのは、まさにこのHerokuで使うためでもあります。まだGitをインストールしていない方は1.3を参照してください)。この章では、最初のアプリケーションをHerokuにデプロイします。

1.4.1 Herokuのセットアップ

HerokuではPostgreSQLデータベースを使用します (ちなみに発音は “post-gres-cue-ell” で、よく“Postgres”と略されます)。そのためには、本番 (production) 環境にpg gemをインストールしてRailsがPostgreSQLと通信できるようにします。

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

rails_12factor gemが追加されている点に注目してください。これは画像やスタイルシートなどの静的なアセットを提供するためにHerokuで使用されます。

1.2.4でも説明したように、アプリケーションが前提としているRubyのバージョンを明示的に指定するのはよい考えです。

ruby '2.0.0'
#ruby-gemset=railstutorial_rails_4_0

(著者の場合、RVMのgemsetも次の行で指定しています。たとえばRuby ’1.9.3’を使用しているのであれば、そのバージョン番号に書き換えてください。本書での指定とは若干異なることになりますが、この変更は問題ありません)。上の一連のコードをリスト1.5Gemfileに適用すると、リスト1.9のようになります。

リスト1.9 Rubyのバージョンを明示的に指定したGemfile
source 'https://rubygems.org'
ruby '2.0.0'
#ruby-gemset=railstutorial_rails_4_0

gem 'rails', '4.0.5'

group :development do
  gem 'sqlite3', '1.3.8'
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

インストールの際には、bundle installに特殊なフラグを追加します。

$ bundle install --without production

--without productionオプションを追加すると、 本番用のgem (この場合はpgrails_12factor) はローカルの環境にはインストールされません (Bundlerでreadlineエラーが発生した場合は、Gemfilegem ’rb-readline’を追加してください)。上で追加したgemは本番環境のみで使用するためのものなので、このコマンドを今実行してもgemはローカルに追加されません。このコマンドを実行するのは、後の本番環境に備えてGemfile.lockを更新し、pg gemとrails_12factor gem、Rubyバージョンの指定を含めておく必要があるためです。以下を実行して変更をコミットできます。

$ git commit -a -m "Update Gemfile.lock for Heroku"

次にHerokuのアカウントを新規作成して設定します。最初にHerokuのユーザー登録を行います。アカウント作成後に完了通知メールが届いたら、Heroku Toolbeltを使用して必要なHerokuソフトウェアをインストールします22。次にターミナルで以下のherokuコマンドを実行します (実行前にターミナルの終了と再起動が必要なことがあります)。

$ heroku login

最後に、元のRailsプロジェクトディレクトリに移動し、herokuコマンドを実行して、Herokuサーバー上にサンプルアプリケーション用の場所を作成します (リスト1.10)。

リスト1.10 Herokuに新しいアプリケーションを作成する。
$ cd ~/rails_projects/first_app
$ heroku create
Created http://stormy-cloud-5881.herokuapp.com/ |
git@heroku.com:stormy-cloud-5881.herokuapp.com
Git remote heroku added

このherokuコマンドを実行すると、Railsアプリケーション専用のサブドメインが作成され、ただちにブラウザで表示可能になります。今はまだ何もありませんが、すぐにデプロイしてWebページを表示させましょう。

1.4.2 Herokuにデプロイする (1)

Railsアプリケーションを実際にHerokuにデプロイするには、まずGitを使用してHerokuにリポジトリをプッシュします。

$ git push heroku master

1.4.3 Herokuにデプロイする (2)

失礼、その2はありません。これで終わりです (図1.10)。デプロイされたアプリケーションの表示は、heroku create (リスト1.10) を実行した際に生成されたアドレスをブラウザで開くだけです (もちろんここに出てくる著者のアドレスではなく、自分が登録したアドレスを使ってください)。herokuコマンドに以下の引数を与えるだけで、正しいアドレスでブラウザが起動します。

$ heroku open

残念ながら、Rails 4.0では技術的な理由により以下のエラーページが表示されます。Rails 4.0のデフォルトページはHerokuでは表示できません。サンプルアプリケーションの一環として、5.3.2でルートへのルーティングを追加すればこのエラーは解消しますのでご安心ください。

heroku_app_4_0
図1.10Heroku上で動作しているRailsチュートリアルの最初のアプリケーション。(拡大)

Herokuへのデプロイが完了した後は、以下のような美しいユーザーインターフェイスを使用してアプリケーションの管理と設定を行えます (図1.11)。

heroku_info_4_0
図1.11見事なHerokuのインターフェイス。(拡大)

1.4.4Herokuコマンド

Herokuのコマンドはたくさんあるので、ここでは簡単に触れる程度にとどめますが、少しだけ使ってみましょう。アプリケーションの名前を変更してみます。

$ heroku rename railstutorial

注意: この名前は、著者のサンプルアプリケーションで既に使用していますので、他の名前を使用してください。実際は、Herokuで生成されたデフォルトのアドレスでも十分です。本当にアプリケーションの名前を変えてみたい場合は、次のようなランダムなサブドメイン名を設定し、この章の冒頭で説明したアプリケーションのセキュリティを実装してみる方法もあります。

hwpcbmze.herokuapp.com
seyjhflo.herokuapp.com
jhyicevg.herokuapp.com

このようなでたらめのサブドメイン名なら、アドレスを渡さない限りサイトがアクセスされる心配もありません。(ちなみに、Rubyの威力の一端をお見せするために、ランダムなサブドメイン名を生成するためのコンパクトなコードを以下に記します。

('a'..'z').to_a.shuffle[0..7].join

最高ですね。)

Herokuでは、サブドメインの他に独自ドメインも使用できます (実を言うと、このRuby on RailsチュートリアルWebサイトもHeroku上に置かれています。本書をオンラインで読んでいるのであれば、まさにHerokuにホスティングされたWebサイトを見ているということになります)。カスタムドメインや他のHeroku関連の情報については、Herokuドキュメントを参照してください。

1.5最後に

この章ではインストール、開発環境の設定、バージョン管理、本番環境へのデプロイなど、多くの課題を達成しました。ここまでの進捗をTwitterに投稿したりFacebookで通知するには以下のリンクからどうぞ。

 

後はRailsを実際に勉強するだけです。一緒に頑張りましょう。

  1. URIはUniform Resource Identifierの略です。それよりやや一般性の低いURLはUniform Resource Locatorの略です。URIは、要するに「ブラウザのアドレスバーにあるあれ」と考えればだいたい合っています。
  2. http://tryruby.org/ 
  3. http://railsforzombies.org/ 
  4. http://railstutorial.jp/screencasts 
  5. Railsチュートリアルを読んでいて、チュートリアル内部の別セクション番号へのリンクをクリックして移動したら、なるべくすぐに元の場所に戻ることをお勧めします。Webページで読んでいる場合は、ブラウザの [戻る] ボタンで戻れます。Adobe ReaderやOS XのプレビューでPDF版を読んでいる場合でも、同様に戻る方法があります。Adobe Readerの場合は、ドキュメント画面を右クリックして [Previous View]をクリックします。OS X Previewの場合はメニューの [移動] > [戻る] で戻れます。
  6. sudoコマンドを実行するとデフォルトでroot (スーパーユーザー) に切り替わるためか、多くの人がsudoコマンドを "superuser do" の略だと誤って信じています。正しくは、sudosuコマンドと英語の “do” をつなげたものです。そしてsuコマンドは “substitute user” (ユーザーの切替) の略なのです。ターミナルでman suと入力すればこのことを確認できます。語源学の示すところによれば “SOO-doo” と発音しますが (“do” は “doo” と発音するので)、“SOO-doh” という発音もよく使われます。
  7. http://railstutorial.jp/help 
  8. https://github.com/perfectionist/sample_project/wiki 
  9. 本書執筆時点では、Sublime Text 3はベータ版どまりです。最新のSublime Textは、新しもの好きの方以外にはお勧めしません。
  10. viは、Unixで古くから使用されているコマンドベースの強力なエディタです。Vimは "vi improved" の略です。
  11. https://github.com/mhartl/rails_tutorial_sublime_text 
  12. https://developer.apple.com/downloads/index.action 
  13. http://strandcode.com/2013/07/11/ruby-version-manager-rvm-overview-for-rails-newbs/ 
  14. この手順が必要となるのは、Rails gemのバージョンを変更した場合に限られます。おそらくRailsインストーラを使用している場合にしかこういうことは起こらないでしょう。他の場合にこの手順を実行しても大丈夫です。
  15. 通常、Webサイトは80番ポートで受信待ちしますが、このポートを使用するには特別な権限が必要になることが多いので、Railsの開発用サーバーでは制限の少ない、番号の大きいポート (いわゆるハイナンバーポート) を使用します。
  16. GUIエディタの起動後もターミナルを使用し続けることはできます。ただし、Gitはデタッチ時にコミットメッセージが空のままファイルを閉じたとみなすため、コミットは中断されます。Gitのエディタオプションでsublgvimにフラグを付けないと、このあたりの動作で頭が混乱するかもしれません (訳注: gitのエディタ設定はGUIエディタとあまり相性がよくないらしく、vimやnanoのようなコマンドベースのエディタを選択するのが無難なようです)。この注釈の意味がよくわからない場合は、無視しても構いません。
  17. .gitignoreがディレクトリに見当たらない場合は、ファイルブラウザやエクスプローラで隠しファイルを表示するよう設定を変更する必要があるかもしれません。
  18. git statusを実行したときに表示して欲しくないファイルがあれば、それらのファイルを1.7.gitignoreファイルに追加してください。
  19. 詳細についてはPro Gitの「Gitのブランチ機能」を参照してください。
  20. Railsチュートリアルのサンプルアプリケーションでは気にする必要はありません。作りかけの恥ずかしいWebアプリケーションをネットにうっかり公開してしまわないだろうかと心配する方もいらっしゃるかと思いますが、それを防ぐための方法はいくつもありますのでご安心ください。1.4.4はその方法の1つです。
  21. “Engine X" と発音します。
  22. https://toolbelt.heroku.com/ 

第2章デモアプリケーション

この章では、Railsの強力な機能をいくつか紹介するためのデモアプリケーションを作成します。scaffoldジェネレータというスクリプトを使ってアプリケーションをすばやく生成する事により、高度なRailsプログラミングとWebプログラミングの概要を学びます。コラム1.1で述べたように、本書の以後の章では基本的にこの逆のアプローチを取り、少しずつアプリケーションを作りながら各段階と概念を説明する予定ですが、scaffoldはRailsアプリケーションの概要を素早くつかむには最適なので、この章でのみあえて使用することにします。生成されたRailsアプリケーションはブラウザのアドレスバーにURLを入力すれば動かすことができるので、これによりRailsアプリの構造、そしてRailsで推奨されているRESTアーキテクチャに関する洞察を得ることにします。

デモアプリケーションは、後に作成するサンプルアプリケーションと同様、ユーザーと、それに関連しているマイクロポストから成り立っています。このデモアプリケーションはもちろん動きますが完成品ではなく、しかも多くの手順が「魔法」のように思えるかもしれません。第3章以降で作成するサンプルアプリケーションでは同等の機能を1つ1つ手動で作成しますので、ご安心ください。その分時間がかかることになりますが、どうか最後まで本書にお付き合いいただければと思います。本書の目的は、scaffoldを使用した即席のアプローチによる表面的な理解ではなく、そこを突破してRailsを深いレベルまで理解することにあります。

2.1 アプリの計画

はじめに、デモアプリケーションをどのようなものにするのか、計画を立てましょう。1.2.3と同じく、railsコマンドでアプリケーションの骨格 (フォルダ構造、デフォルトで作成されるファイル類) を生成します。

$ cd rails_projects
$ rails new demo_app
$ cd demo_app

次に、Bundlerで使用するGemfile をテキストエディタで編集します。リスト2.1の内容に書き換えてください。

リスト2.1 デモアプリケーション用のGemfile
source 'https://rubygems.org'
ruby '2.0.0'
#ruby-gemset=railstutorial_rails_4_0

gem 'rails', '4.0.5'

group :development do
  gem 'sqlite3', '1.3.8'
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

リスト2.1の内容はリスト1.9と同じです。

1.4.1でも説明したとおり、--without productionオプションを追加することで、本番用のgemを除いたローカルgemをインストールします。

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

(第1章でも書きましたが、Bundlerでreadlineエラーが発生した場合は、Gemfilegem ’rb-readline’を追加してください)。

最後に、このデモアプリケーションをバージョン管理下に置きます。既に説明したとおり、railsコマンドを実行するとデフォルトの.gitignoreファイルが生成されます。システム環境によっては、このファイルをリスト1.7のように変更しておくと便利なことがあります。Gitリポジトリを初期化して最初のコミットを実行しておきます。

$ git init
$ git add .
$ git commit -m "Initial commit"
create_demo_repo_4_0
図2.1GitHubでデモアプリ用リポジトリを作成する。(拡大)

必須ではありませんが、リポジトリを新規作成して (図2.1) GitHubにプッシュすることもできます。

$ git remote add origin https://github.com/<username>/demo_app.git
$ git push -u origin master

(最初のアプリケーションのときと同様、GitHubリポジトリを初期化するときにREADMEを使用しないように注意してください。)

これで、アプリ自体を作成するための下準備が整いました。Webアプリケーションを作る際、アプリケーションで使用される構造を表すためのデータモデルを最初に作成しておくのが普通です。今回のデモアプリケーションでは、ユーザーと短いマイクロポストのみをサポートするマイクロブログを作成します。そこで、まずアプリケーションのユーザーで使用するモデルを作成 (2.1.1) し、次にマイクロポストで使用するモデルを作成します (2.1.2)。

2.1.1ユーザーのモデル設計

Webでのユーザー登録の方法が多岐にわたることからもわかるように、ユーザーという概念をデータモデルで表す方法はたくさんありますが、ここではあえて最小限の構造に抑えておきます。各ユーザーには、重複のない一意のキーとなるinteger型のID番号 (idと呼びます) を割り当て、このIDに加えて一般公開されるstring型の名前 (name)、そして同じくstring型のメールアドレス (email) を持たせます。メールアドレスはユーザー名としても使われます。ユーザーのデータモデルの概要を図2.2に示します。

demo_user_model
図2.2: ユーザー用のデータモデル

詳しくは6.1.1から解説しますが、図2.2のユーザー (users) はデータベースのテーブル (table) に相当します。また、idnameemailの属性はそれぞれテーブルのカラム (column: 列) に相当します。

2.1.2マイクロポストのモデル設計

マイクロポストのデータモデルはユーザー用のデータモデルよりもさらにシンプルです。idと、マイクロポストのテキスト内容を格納するstring型のcontentだけで構成されています1。しかし実際には、マイクロポストをユーザーと関連付ける (associate) 必要があるため、ポストのオーナーを記録するためのuser_idも追加します。これにより、データモデルは図2.3のようになります。

demo_micropost_model
図2.3 マイクロポストのデータモデル

2.3.3では、user_idという属性を使用して、1人のユーザーに複数のマイクロポストが関連付けられるという構造を簡潔に説明します。詳細は第10章で完全に説明します。

2.2Users リソース

ここでは、2.1.1で説明したユーザー用のデータモデルを、そのモデルを表示するためのWebインターフェイスに従って実装します。 このデータモデルとWebインターフェイスは、組み合わさってUsersリソースとなり、ユーザーというものを、HTTPプロトコル経由で自由に作成/読み出し/更新/削除できるオブジェクトとみなすことができるようになります。「はじめに」で約束したとおり、このUsersリソースはすべてのRailsプロジェクトに標準装備されているscaffoldジェネレータで生成します。scaffoldで生成された膨大なコードを今詳細に読む必要はありません。今の段階ではおそらく混乱するだけでしょう。

Railsのscaffoldは、rails generateスクリプトにscaffoldコマンドを渡すことで生成されます。scaffoldコマンドの引数には、リソース名を単数形にしたもの (この場合はUser) を使用し、必要に応じてデータモデルの属性をオプションとしてパラメータに追加します2

$ rails generate scaffold User name:string email:string
      invoke  active_record
      create    db/migrate/20130305221714_create_users.rb
      create    app/models/user.rb
      invoke      test_unit
      create      test/models/user_test.rb
      create      test/fixtures/users.yml
      invoke  resource_route
       route  resources :users
      invoke  jbuilder_scaffold_controller
      create  app/controllers/users_controller.rb
      invoke    erb
      create      app/views/users
      create      app/views/users/index.html.erb
      create      app/views/users/edit.html.erb
      create      app/views/users/show.html.erb
      create      app/views/users/new.html.erb
      create      app/views/users/_form.html.erb
      invoke      test_unit
      create      test/controllers/users_controller_test.rb
      invoke  helper
      create      app/helpers/users_helper.rb
      invoke      test_unit
      create        test/helpers/users_helper_test.rb
      invoke    jbuilder
       exist      app/views/users
      create      app/views/users/index.json.jbuilder
      create      app/views/users/show.json.jbuilder
      invoke  assets
      invoke    coffee
      create      app/assets/javascripts/users.js.coffee
      invoke    scss
      create      app/assets/stylesheets/users.css.scss
      invoke  scss
      create    app/assets/stylesheets/scaffolds.css.scss

name:stringemail:stringオプションを追加することで、Userモデルの内容が図2.2の表のとおりになるようにします (なお、idパラメータはRailsによって自動的に主キーとしてデータベースに追加されるため、追加不要です)。

続いてデモアプリケーションの開発を進めるには、以下のようにRakeを使用してデータベースをマイグレート (migrate) する必要があります (コラム2.1)。

$ bundle exec rake db:migrate
==  CreateUsers: migrating =================================
-- create_table(:users)
   -> 0.0017s
==  CreateUsers: migrated (0.0018s) ========================

このコマンドは、単にデータベースを更新し、usersデータモデルを作成するためのものです (データベースのマイグレーションの詳細については 6.1.1以降で説明します)。なお、現在のGemfileに対応するバージョンのRakeが確実に実行されるようにするために、bundle execを使用してrakeを実行します (1.2.2.3で勧めたとおりにRVMを使用している場合は、bundle execは省略することもできます。本書では、基本的に省略せずに書くことにします。bundle execを実行しなくて良いようにする他の方法については、3.6.1を参照してください)。

ここまで実行すれば、以下のようにrails sコマンド (rails serverコマンドの短縮版) を実行してローカルWebサーバーを起動できるようになります。

$ rails s

これでhttp://localhost:3000/でデモアプリケーションをブラウザ表示できるようになっているはずです。

2.2.1ユーザーページを表示する

http://localhost:3000/をブラウザで表示すると、デフォルトのRailsページが表示されます (図1.3)。実は、Usersリソースを作成した時に、これ以外のさまざまな「ユーザーを扱うためのページ」が既に作成されています。たとえば、/usersを表示すればすべてのユーザーの一覧が表示されますし、/users/newを表示すれば新規ユーザー作成ページが表示されます。この節では以後、ユーザーに関連するページについて手短に説明します。その際、表2.1に記載されている、ページとURLの関係を参照するとわかりやすいと思います。

URLアクション用途
/usersindexすべてのユーザーを一覧するページ
/users/1showid=1のユーザーを表示するページ
/users/newnew新規ユーザーを作成するページ
/users/1/editeditid=1のユーザーを編集するページ
表2.1Usersリソースにおける、ページとURLの関係

まずはユーザーの一覧を表示するindexページから見てみましょう。もちろん、この時点ではまだユーザーは登録されていません (図2.4)。

demo_blank_user_index_rails_3
図2.4Usersリソース (/users) ページの最初の状態。(拡大)

ユーザーを新規作成するには、図2.5newページを表示します (注: ローカルのコンピュータで開発を行なっている場合は、URLのうちhttp://localhost:3000の部分は常に同じであるため、以後この部分は省略します)。 第7章では、このページをユーザー登録ページに転用します。

demo_new_user_rails_3
図2.5新規ユーザー作成ページ (/users/new)。(拡大)

テキストフィールドに名前とメールアドレスを入力して [Create User] ボタンを押してください。ユーザーが作成され、図2.6のようにshowページが表示されます (緑色のウェルカムメッセージは、 7.4.2で解説するflashという機能を使用して表示しています)。ここで、URLが/users/1と表示されていることに注目してください。ご想像のとおり、この数字1図2.2id属性そのものです。7.1では、このページをユーザーのプロファイルに作り変える予定です。

demo_show_user_rails_3
図2.6ユーザー表示用のページ (/users/1)。(拡大)

今度は、ユーザー情報を変更するためにeditページを表示してみましょう (図2.7)。この編集ページ上でユーザーに関する情報を変更し、[Update User] ボタンを押せば、デモアプリケーション内のユーザー情報が変更されます (図2.8)。(詳細は第6章で説明しますが、このユーザー情報は、Webアプリケーションの背後にあるデータベースに保存されています。) このサンプルアプリケーションでも、ユーザーを編集または更新する機能を9.1で実装します。

demo_edit_user_rails_3
図2.7ユーザー編集用のページ (/users/1/edit)。(拡大)
demo_update_user_rails_3
図2.8情報が更新されたユーザー。(拡大)

ここでnewページに戻り、ユーザーをもう1人作成してみましょう。indexページを表示してみると、図2.9のようにユーザーが追加されています。7.1ではもっと本格的なユーザー一覧ページを作成する予定です。

demo_user_index_two_rails_3
図2.92人目のユーザーが追加された一覧ページ (/users)。(拡大)

ユーザーの作成、表示、編集方法について説明しましたので、今度はユーザーを削除してみましょう (図2.10)。 図2.10の [Destroy] リンクをクリックするとユーザーが削除され、indexページのユーザーは1人だけになります (もしこのとおりにならない場合は、ブラウザのJavaScriptが有効になっているかどうかを確認してください。Railsでは、ユーザーを削除するリクエストを発行するときにJavaScriptを使用しています)。なお、9.4ではサンプルアプリケーションにユーザーを削除する機能を実装し、管理権限 (admin) を持つユーザー以外は削除を実行できないように制限をかけます。

demo_destroy_user_rails_3
図2.10ユーザーを削除する。(拡大)

2.2.2 MVCの挙動

これでUsersリソースの概略についての説明が終わりましたが、ここで1.2.6で紹介した MVC (Model-View-Controller = モデル-ビュー-コントローラ) パターンの観点からこのリソースを考察してみましょう。具体的には、/usersのindexページをブラウザで開くという典型的な操作を行うときに何が起こっているかをMVC (図2.11) を使って説明します。

mvc_detailed
図2.11RailsにおけるMVC。(拡大)
  1. ブラウザは/usersというURLへのリクエストを発行する。
  2. Railsは/usersをUsersコントローラ内のindexアクションに割り当てる (ルーティング)。
  3. indexアクションはUserモデルに「すべてのユーザーを取り出せ」と指示する (User.all)。
  4. Userモデルはすべてのユーザーをデータベースから取り出す。
  5. Userモデルはユーザーの一覧をコントローラに返す。
  6. コントローラはユーザーの一覧を@users変数に保存し、indexビューに渡す。
  7. ビューは、その中に埋め込まれているRubyを使用してHTMLを生成する。
  8. コントローラは、生成されたHTMLをブラウザに返す3

最初にブラウザからのリクエストを見てみましょう。このリクエストは、アドレスバーにURLを入力したりリンクをクリックした時に発生します (図2.11の1)。リクエストはRailsルーターに到達し (2)、ここでURL (とリクエストの種類: コラム3.3参照) に基づいて適切なコントローラのアクションに割り当てられます (ディスパッチ)。ユーザーのURLをUsersリソースで使用するコントローラアクションに割り当てる (マッピングする) ためのコードはリスト2.2です。このコードは、URLとアクションの組み合わせ (表2.1) を効率的に作成します。(:usersという一見奇妙な記法は、シンボルと呼ばれるものです。詳細については4.3.3で説明します。)

リスト2.2 Railsルートで使用するUsersリソース用のルール。
config/routes.rb
DemoApp::Application.routes.draw do
  resources :users
  .
  .
  .
end

2.2.1以降で紹介した各ページは、Usersコントローラの中にあるアクションにそれぞれ対応しています。1つのコントローラには関連する多数のアクションがまとめられています。リスト2.3は、scaffoldで生成したコントローラの骨格です。class UsersController < ApplicationControllerという記法は、Rubyのクラスでの継承の使用例となっていることにも注目してください (継承の概略については2.3.4、詳細については4.4で説明します)。

リスト2.3 Usersコントローラの骨格。
app/controllers/users_controller.rb
class UsersController < ApplicationController
.
.
.

  def index
    .
    .
    .
  end

  def show
    .
    .
    .
  end

  def new
    .
    .
    .
  end

  def create
    .
    .
    .
  end

  def edit
    .
    .
    .
  end

  def update
    .
    .
    .
  end

  def destroy
    .
    .
    .
  end
end

ページの数よりもアクションの数の方が多いことにお気付きでしょうか。indexshowneweditアクションはいずれも2.2.1のページに対応していますが、それ以外にもcreateupdatedestroyアクションがあります。通常、これらのアクションは、ページを出力せずにデータベース上のユーザー情報を操作します (もちろん、ページを出力しても良いのですが)。表2.2は、RailsにおけるRESTアーキテクチャ (コラム2.2) を構成するすべてのアクションの一覧です。RESTは、コンピュータ科学者Roy Fielding4によって提唱された「REpresentational State Transfer」という概念に基づいています。表2.2のURLには重複しているものがあることに注目してください。たとえば、showアクションと updateアクションは、どちらも/users/1というURLに対応しています。これらのアクション同士の違いは、それらのアクションに対応するHTTP requestメソッドの違いでもあります。HTTP requestメソッドの詳細については3.2.1で説明します。

HTTPリクエストURLアクション用途
GET/usersindexすべてのユーザーを表示するページ
GET/users/1showid=1のユーザーを表示するページ
GET/users/newnewユーザーを新規作成するページ
POST/userscreateユーザーを作成するアクション
GET/users/1/editeditid=1のユーザーを編集するページ
PATCH/users/1updateid=1のユーザーを更新するアクション
DELETE/users/1destroyid=1のユーザーを削除するアクション
表2.2リスト2.2のUsersリソースに含まれるRESTfulなルーティング

UsersコントローラとUserモデルの関係をさらに考察するために、indexアクションを整理してみました (リスト2.4) (scaffoldで自動生成されるコードは冗長で紛らわしいので除いてあります)。

リスト2.4 デモアプリケーションのユーザーindexアクションを整理したもの。
app/controllers/users_controller.rb
class UsersController < ApplicationController
.
.
.

  def index
    @users = User.all
  end
  .
  .
  .
end

indexアクションに@users = User.allという行があります (図2.11 の③に相当)。これによって、Userモデルからすべてのユーザーの一覧を取り出し (④)、@usersという変数に保存します (⑤)。なお、@usersは「あっと ゆーざーず」と発音します。Userモデルの内容はリスト2.5にあります。驚くほどシンプルな内容ですが、継承 (2.3.4および4.4) によって多くの機能が備わっています。特に、Active RecordというRubyライブラリのおかげで、リスト2.5のUserモデルはUser.allというリクエストに対してすべてのユーザーを返すことができます。

リスト2.5 デモアプリケーションのUserモデル。
app/models/user.rb
class User < ActiveRecord::Base
end

@users変数にユーザー一覧が保存されると、コントローラはリスト2.6ビューを呼び出します (⑥)。@記号で始まる変数はRubyではインスタンス変数と呼ばれます。ビューでは自動的にこれらのインスタンス変数を使用できます。この場合、リスト2.6index.html.erbビューは、@usersの一覧を並べ、1行ごとにHTMLの行として出力します (今はこのコードの意味がわからなくても問題ありません。これはあくまで説明のためのものです)。

リスト2.6 ユーザーのindexのビュー。
app/views/users/index.html.erb
<h1>Listing users</h1>

<table>
  <tr>
    <th>Name</th>
    <th>Email</th>
    <th></th>
    <th></th>
    <th></th>
  </tr>

<% @users.each do |user| %>
  <tr>
    <td><%= user.name %></td>
    <td><%= user.email %></td>
    <td><%= link_to 'Show', user %></td>
    <td><%= link_to 'Edit', edit_user_path(user) %></td>
    <td><%= link_to 'Destroy', user, method: :delete,
                                     data: { confirm: 'Are you sure?' } %></td>
  </tr>
<% end %>
</table>

<br />

<%= link_to 'New User', new_user_path %>

ビューはその内容をHTMLに変換し (⑦)、コントローラがブラウザにHTMLを送信して、ブラウザでHTMLが表示されます (⑧)。

2.2.3Users リソースの欠点

scaffoldで作成したUsersリソースは、Railsの概要を手っ取り早く説明するには良いのですが、以下のようなさまざまな問題点を抱えています。

  • データの検証が行われていない。 このままでは、ユーザー名が空欄であったり、でたらめなメールアドレスを入力したりしても通ってしまいます。
  • ユーザー認証が行われていない。 サインイン、サインアウトが行われていないので、誰でも無制限に操作できてしまいます。
  • テストが行われていない。 厳密にはこれは正しい表現ではありません。というのも、scaffoldのコードには初歩的なテストが一応含まれているからです。ただ、scaffoldのテストコードは読みづらく、柔軟性もありません。さらにデータの検証、ユーザー認証、その他に必要な独自テストも含まれていません。
  • レイアウトが整えられていない。 サイトデザインも操作法も一貫していません。
  • 理解が困難。 scaffoldのコードを理解できるぐらいなら、そもそも本書を読む必要はないでしょう。

2.3Microposts リソース

Usersリソースを生成して内容を理解しましたので、今度はMicropostsリソースで同じことをやってみましょう。なお、この節全体について、Micropostsリソースを理解する際には2.2のuser要素と比較しながら進めることをお勧めします。実際、これらの2つのリソースはさまざまな面で似通っています。RailsのRESTful構造を身体に叩きこむには、繰り返し学ぶのが一番です。UsersリソースとMicropostsリソースの構造の類似点を理解することが、この章の主要な目的です (もちろん、この章のような初歩的なサンプルアプリケーションではない、頑丈なアプリケーションを開発するのは簡単ではありません。Micropostsリソースについては第10章で再び取り扱いますが、その頃にはMicropostsは作り込みが進んで外観がすっかり変わってしまいます)。

2.3.1マイクロポストのページを表示する

Usersリソースの場合と同様に、Micropostsリソースもscaffoldでコードを生成してみましょう。rails generate scaffoldコマンドを使用して、図2.3のデータモデルを実装してみます5

$ rails generate scaffold Micropost content:string user_id:integer
      invoke  active_record
      create    db/migrate/20130307005528_create_microposts.rb
      create    app/models/micropost.rb
      invoke      test_unit
      create      test/models/micropost_test.rb
      create      test/fixtures/microposts.yml
      invoke  resource_route
       route  resources :microposts
      invoke  jbuilder_scaffold_controller
      create    app/controllers/microposts_controller.rb
      invoke    erb
      create      app/views/microposts
      create      app/views/microposts/index.html.erb
      create      app/views/microposts/edit.html.erb
      create      app/views/microposts/show.html.erb
      create      app/views/microposts/new.html.erb
      create      app/views/microposts/_form.html.erb
      invoke      test_unit
      create      test/controllers/microposts_controller_test.rb
      invoke  helper
      create      app/helpers/microposts_helper.rb
      invoke      test_unit
      create        test/helpers/microposts_helper_test.rb
      invoke    jbuilder
       exist      app/views/microposts
      create      app/views/microposts/index.json.jbuilder
      create      app/views/microposts/show.json.jbuilder
      invoke  assets
      invoke    coffee
      create      app/assets/javascripts/microposts.js.coffee
      invoke    scss
      create      app/assets/stylesheets/microposts.css.scss
      invoke  scss
   identical    app/assets/stylesheets/scaffolds.css.scss

新しいデータモデルでデータベースを更新するには、2.2のときと同様にマイグレーションを実行します。

$ bundle exec rake db:migrate
==  CreateMicroposts: migrating =============================
-- create_table(:microposts)
   -> 0.0023s
==  CreateMicroposts: migrated (0.0026s) ======================

これでMicropostsを作成する準備ができました。作成方法は2.2.1と同じです。Railsのroutesファイルは期待どおりにscaffoldジェネレータによって更新され、リスト2.7のようにMicropostsリソース用のルールが追加されました6。ユーザーの場合と同様、resources :micropostsというルーティングルールは、表2.3に示したようにマイクロポスト用のURLをMicropostsコントローラ内のアクションに割り当てます。

リスト2.7 Micropostsリソース用のルールが追加されたRailsのroutesファイル。
config/routes.rb
DemoApp::Application.routes.draw do
  resources :microposts
  resources :users
  .
  .
  .
end
HTTPリクエストURLアクション用途
GET/micropostsindexすべてのマイクロポストを表示するページ
GET/microposts/1showid=1のマイクロポストを表示するページ
GET/microposts/newnewマイクロポストを新規作成するページ
POST/micropostscreateマイクロポストを新規作成するアクション
GET/microposts/1/editeditid=1のマイクロポストを編集するページ
PATCH/microposts/1updateid=1のマイクロポストを更新するアクション
DELETE/microposts/1destroyid=1のマイクロポストを削除するアクション
表2.3リスト2.7のMicropostsリソースに含まれるRESTfulなルーティング

Micropostsコントローラ自体の構造をリスト2.8に示します。リスト2.8の内容は、UsersControllerMicropostsControllerに置き換わっているだけで、リスト2.3完全に同一である点に注目してください。これは、RESTアーキテクチャが2つのリソースに同じように反映されていることを示しています。

リスト2.8 Micropostsコントローラの骨格。
app/controllers/microposts_controller.rb
class MicropostsController < ApplicationController
.
.
.

  def index
    .
    .
    .
  end

  def show
    .
    .
    .
  end

  def new
    .
    .
    .
  end

  def create
    .
    .
    .
  end

  def edit
    .
    .
    .
  end

  def update
    .
    .
    .
  end

  def destroy
    .
    .
    .
  end
end

/microposts/newページをブラウザで開き、新しいマイクロポストの情報を入力してマイクロポストをいくつか作成してみましょう (図2.12)。

demo_new_micropost_rails_3
図2.12新しいマイクロポストの作成ページ (/microposts/new)。(拡大)

ここではひとまずマイクロポストを1つか2つ作成し、少なくとも片方のuser_id1になるようにして、2.2.1で作成した最初のユーザーのidと同じにします。結果は図2.13のようになるはずです。

demo_micropost_index_rails_3
図2.13マイクロポストのindexページ (/microposts)。(拡大)

2.3.2マイクロポストをマイクロにする

マイクロポストのマイクロという名前にふさわしく、何らかの方法で文字数制限を与えてみましょう。Railsでは、検証 (validates) を使用して簡単にこのような入力制限を追加することができます。Twitterのように140文字の制限を与えるには、lengthを指定します。テキストエディタかIDEを使用してapp/models/micropost.rbを開き、 リスト2.9の内容に置き換えます。(注: リスト2.9のようなvalidatesはRails 3の機能です。Rails 2.3の場合は validates_length_ofを使用します。)

リスト2.9 マイクロポストの最大文字数を140文字に制限する。
app/models/micropost.rb
class Micropost < ActiveRecord::Base
  validates :content, length: { maximum: 140 }
end

リスト2.9のコードは、これで本当に動作するのかと思えるかもしれませんが、ちゃんと動作します (検証機能については6.2でさらに詳しく説明します)。141文字以上の新規マイクロポストを投稿してみればわかります。図2.14に示したとおり、マイクロポストの内容が長すぎるというエラーメッセージがRailsによって表示されます (エラーメッセージの詳細については7.3.3で説明します)。

micropost_length_error_rails_3
図2.14マイクロポストの作成に失敗した場合のエラーメッセージ。(拡大)

2.3.3ユーザーとマイクロポストをhas_manyで関連づける

異なるデータモデル同士の関連付けは、Railsの強力な機能です。ここでは、1人のユーザーに対し複数のマイクロポストがあるとしましょう。UserモデルとMicropostモデルをそれぞれリスト2.10リスト2.11のように更新することでこの関連付けを表現できます。

リスト2.10 1人のユーザーに複数のマイクロポストがある。
app/models/user.rb
class User < ActiveRecord::Base
  has_many :microposts
end
リスト2.11 1つのマイクロポストは1人のユーザーにのみ属する。
app/models/micropost.rb
class Micropost < ActiveRecord::Base
  belongs_to :user
  validates :content, length: { maximum: 140 }
end

この関連付けを図で表したものが図2.15です。micropostsテーブルにはuser_idカラムを作成してあったので、それによってRailsとActive Recordがマイクロポストとユーザーを関連付けることができるようになっています。

micropost_user_association
図2.15マイクロポストとユーザーの関連付け。

第10章第11章では、関連付けられたユーザーとマイクロポストを同時に表示し、Twitterのようなマイクロポストのフィードを作成する予定です。ここでは、Railsのconsoleを使用して、ユーザーとマイクロポストの関連付けを確認するにとどめます。Railsのconsoleは、Railsアプリケーションを対話的に操作することができる便利なツールです。まず、ターミナルでrails consoleコマンドを入力します。続いて、User.firstを使用してデータベースから1人目のユーザーの情報を取り出し、first_user変数に保存します7

$ rails console
>> first_user = User.first
=> #<User id: 1, name: "Michael Hartl", email: "michael@example.org",
created_at: "2013-03-06 02:01:31", updated_at: "2013-03-06 02:01:31">
>> first_user.microposts
=> [#<Micropost id: 1, content: "First micropost!", user_id: 1, 
created_at: "2011-11-03 02:37:37", updated_at: "2011-11-03 02:37:37">, 
"2013-03-06 02:37:37", updated_at: "2013-03-06 02:37:37">,
#<Micropost id: 2,content: "Second micropost", user_id: 1, 
created_at: "2013-03-06 02:38:54",updated_at: "2013-03-06 02:38:54">]
>> exit

(最後の行のようにexitを実行するとrails consoleを終了できます。多くのシステムでは、Ctrl-dキーを押して終了することもできます。) first_user.micropostsというコードを実行すると、そのユーザーに関連付けられているマイクロポストにアクセスできます。このときActive Recordは、user_idfirst_userのid (ここでは1) と等しいマイクロポストを自動的に返します。Active Recordの関連付け機能については第10章第11章でさらに詳しく解説します。

2.3.4継承の階層

最後に、デモアプリケーションで使用しているRailsのコントローラとモデルのクラス階層について簡単に解説します。この節を理解するには、多少なりともオブジェクト指向プログラミング (OOP) の経験が必要です。オブジェクト指向プログラミングを学んだことのない方はこの節をスキップしても構いません。特に、クラスの概念 (4.4で解説します) に慣れていない方は、後でこの節をもう一度読み返してください。

最初に、モデルの継承構造について説明します。リスト2.12リスト2.13を比較してみると、UserモデルとMicropostモデルはいずれもActiveRecord::Baseというクラスを継承しています (継承関係は<記号で表現されています)。このクラスは、ActiveRecordが提供するベースクラスであり、クラス間のリレーションは図2.16のようになります。ActiveRecord::Baseクラスを継承したことによって、作成したモデルオブジェクトはデータベースにアクセスできるようになり、データベースのカラムをあたかもRubyの属性のように扱えるようになります。

リスト2.12 Userクラスにおける継承。
app/models/user.rb
class User < ActiveRecord::Base
  .
  .
  .
end
リスト2.13 Micropostクラスにおける継承。
app/models/micropost.rb
class Micropost < ActiveRecord::Base
  .
  .
  .
end
demo_model_inheritance
図2.16UserモデルとMicropostモデルの継承階層。

コントローラの継承構造はもう少しだけ複雑です。リスト2.14リスト2.15を比較してみると、UsersコントローラとMicropostsコントローラはいずれもApplicationControllerを継承しています。リスト2.16を見ると、ApplicationController自身はActionController::Baseを継承しています。これはRailsのAction Packというライブラリが提供している、コントローラ用のベースクラスです。これらのクラス同士の関係を図2.17に示します。

リスト2.14 UsersControllerクラスにおける継承。
app/controllers/users_controller.rb
class UsersController < ApplicationController
  .
  .
  .
end
リスト2.15 MicropostsControllerクラスにおける継承。
app/controllers/microposts_controller.rb
class MicropostsController < ApplicationController
  .
  .
  .
end
リスト2.16 ApplicationControllerクラスにおける継承。
app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
  .
  .
  .
end
demo_controller_inheritance
図2.17UsersコントローラとMicropostsコントローラにおける継承関係。

モデルの継承関係と同様に、UsersコントローラもMicropostsコントローラも最終的にはActionController::Baseを継承しており、モデルオブジェクトの操作、インバウンドHTTP requestのフィルタ、ビューをHTMLとして出力するなどの多彩な機能を実行できるようになっています。Railsのコントローラは必ずApplicationControllerを継承しているので、Applicationコントローラで定義されたルールは自動的にアプリケーションのすべてのアクションに反映されます。たとえば8.2.1では、サンプルアプリケーションのすべてのコントローラにサインインとサインアウト用のヘルパーを含めています。

2.3.5デモアプリケーションのデプロイ

Micropostsリソースの説明が終わりましたので、ここでリポジトリをGitHubに登録しましょう。

$ git add .
$ git commit -m "Finish demo app"
$ git push

通常は、更新をあまりためないように、Gitのコミットをなるべく頻繁に行うことが望ましいのですが、この章の締めくくりとしてサイズの大きなコミットを1度だけ行っても問題ありません。

この時点で、デモアプリケーションを1.4のようにHerokuにデプロイしてもかまいません。

$ heroku create
$ git push heroku master

アプリケーションのデータベースが動作するようにするには、以下を実行して本番データベースのマイグレーションを行う必要もあります。

$ heroku run rake db:migrate

このコマンドを実行すると、Heroku上のデータベースが、ユーザーとマイクロポストのデータモデルで更新されます。

2.4最後に

ついにRailsアプリケーションを最後まで完成させました。この章で作成したデモアプリケーションには良いところもありますが、さまざまな弱点もあります。

良い点

  • Rails全体を高度なレベルで概観できた
  • MVCモデルを紹介できた
  • RESTアーキテクチャに初めて触れた
  • データモデルの作成を初めて行った
  • データベースを背後に持つWebアプリケーションを本番環境で動かした

課題

  • レイアウトやスタイルが皆無
  • “Home” や “About” のような静的なページがない
  • ユーザーにパスワードを設定できない
  • ユーザーの画像も置けない
  • サインインできない
  • セキュリティがない
  • ユーザーとマイクロポストを自動的に関連付けていない
  • “following” 機能も “followed” 機能もない
  • マイクロポストをフィードできない
  • テスト駆動開発が行われていない
  • 理解が困難

本書では以後、良い点を保ちつつこれらの弱点を克服していきます。

  1. もっと多い文字数の投稿できるモデルにしたい場合は (たとえば、マイクロポストではなく、ブログのような投稿を許可したい場合は)、stringの代わりにtextを使うとよいでしょう。
  2. scaffoldにおける命名は、モデル名の命名の習慣に従っています。リソースやコントローラは複数形で表し、モデルは単数形で表すのが普通です。UsersではなくUserとしたのはこのためです。
  3. ビューは、(ApacheやNginxなどのWebサーバーを経由してはいるが) ブラウザにHTMLを直接返すと説明している文献もあります。私は、Railsの実際の実装とは無関係に、コントローラを情報の流れの中心となるハブとみなすことを好んでいます。
  4. Fielding, Roy Thomas. Architectural Styles and the Design of Network-based Software Architectures. Doctoral dissertation, University of California, Irvine, 2000. 
  5. ユーザーでscaffoldを実行した場合と同様に、scaffoldジェネレータはマイクロポストでもRailsモデルを単数形とする習慣に従います。実行したコマンドがgenerate Micropostと単数形になっていたのはこのためです。
  6. scaffoldで生成したコードにはリスト2.7よりも多くの改行が追加されます。Rubyは単なる改行を無視するので、問題はありません。
  7. 実際のターミナル上では、プロンプトがruby-2.0.0-head >などと表示される可能性がありますが、Rubyのバージョンが環境によって異なる可能性があるため、例のプロンプトは>>と表記しています。

第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関数を使用し、それを<%= ... %>表記 (上と微妙に異なることに注目してください) で囲むことにより、テンプレートにタイトルを挿入します15

<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 

第4章Rails風味のRuby

この章では、第3章で使用した例を基に、Railsにおいて重要となるRubyのさまざまな要素について探っていくことにしましょう。Rubyは巨大な仕様を持つ言語ですが、幸い、Rails開発者にとって必要な知識は比較的少なくて済みます。さらに、Railsのために必要なRubyの知識は、通常のRubyを学ぶ過程とは異なります。動的なWebアプリを作ることができればそれでよいというのであれば、まずRailsを学ぶようにし、Rubyについては当分の間、必要が生じた場合にのみ学習することをお勧めします。そうではなく、真のRailsエキスパートになりたいのであれば、Rubyをさらに深いレベルまで理解する必要があります。本書は、そのための開発技術の基礎を築く助けになるでしょう。1.1.1でも示したように、Railsチュートリアル を終えた後には「 Beginning Ruby」、「The Well-Grounded Rubyist」、「The Ruby Way」などの純粋なRubyについての本を読むことをお勧めします。

この章には多くの話題が盛り込まれていますが、一度読んだだけで理解する必要はまったくありません。今後もこの章には頻繁に立ち戻って参照します。

4.1動機

前章でお見せしたとおり、Rubyの基礎知識がまったくない状態であったにもかかわらずRailsアプリケーションの骨組みを作り上げ、さらにテストまで行うことができました。このときは、本書が提供するテストコードと、テストスイートがパスするまでエラーメッセージの修正を繰り返すという方法だけを頼りに作業を進めました。しかしこのような初歩的な作業をいつまでも続けるわけにはいきませんので、今の私たちのRubyに関する知識と経験の限界に真正面から挑み、これを乗り越えるためにこの章を割り当てることにします。

前章の終わりでは、Railsのレイアウトを使用してビューでの重複を取り除くために、リスト4.1に示したように、ほぼ静的なページを単に更新したにとどまりました。これは、リスト3.26をほんのわずか修正しただけのものです。

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

リスト4.1の以下の行に注目してください。

<%= stylesheet_link_tag "application", media: "all",
                                       "data-turbolinks-track" => true %>

ここでは、Railsの組み込み関数stylesheet_link_tag (詳細はRails APIを参照) を使用して、application.cssをすべてのメディアタイプにインクルードしています (メディアタイプには、コンピュータの画面や印刷画面なども含まれます)。Rails開発経験者にとってこの行は実にシンプルですが、しかしここには少なくとも混乱を生じる可能性のあるRubyの概念が4つあります。Railsの組み込み関数、括弧を使わない関数呼び出し、シンボル、そしてハッシュです。これらの概念についてはこの章ですべて説明します。

Railsのビューでは膨大な組み込み関数を使用することができますが、それに加えて新しい関数を作成することもできます。この関数はヘルパーと呼ばれます。カスタムヘルパーを作成する方法を学ぶために、まずリスト4.1のタイトル行の部分に注目しましょう。

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

上の行は、ページタイトルの定義に依存しています。この定義は、以下のようにビューでprovideを使用して行われています。

<% 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>

このとき、もしタイトルをまったく与えていなければ、タイトルが空欄になってしまいます。これを防ぐには、すべてのページで使用する基本タイトルを定め、特定のページでは異なるタイトルに変更できるようなオプションを与えるのが常套手段です。これは現在のレイアウトでも、ある点を除いて達成されています。もしビューの1つからprovide呼び出しを削除すると、そのページ固有のタイトルの代わりに以下のタイトルが表示されます。

Ruby on Rails Tutorial Sample App | 

基本タイトルとしてはこれで正しいのですが、末尾に余分な縦棒|が残ってしまっています。

ページタイトルが正しく表示されない問題を解決するために、 full_titleというヘルパーを作成することにします。full_titleヘルパーは、ページタイトルが定義されていない場合は基本タイトル “Ruby on Rails Tutorial Sample App”を返し、定義されている場合は基本タイトルに縦棒と追加ページタイトルを追加して返します (リスト4.2)1

リスト4.2 full_titleヘルパーを定義する。
app/helpers/application_helper.rb
module ApplicationHelper

  # ページごとの完全なタイトルを返します。
  def full_title(page_title)
    base_title = "Ruby on Rails Tutorial Sample App"
    if page_title.empty?
      base_title
    else
      "#{base_title} | #{page_title}"
    end
  end
end

ヘルパーを作成したので、これを使用してレイアウトをシンプルにすることができます。

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

上の行を以下で置き換えます。

<title><%= full_title(yield(:title)) %></title>

置き換えの結果をリスト4.3に示します。

リスト4.3 サンプルアプリケーションのWebサイトのレイアウト。
app/views/layouts/application.html.erb
<!DOCTYPE html>
<html>
  <head>
    <title><%= full_title(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>

このヘルパーを定義することで、Homeページにこれまで表示されていた余分な "Home" という単語を表示せず、基本タイトルのみを正しく表示することもできるようになります。これを行うには、まずリスト4.4に示すように以前のテストコードを更新し、'Home'の文字が表示されていないことを確認するテストを追加します。

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

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

    it "should not have a custom page title" do
      visit '/static_pages/home'
      expect(page).not_to have_title('| Home')
    end
  end
  .
  .
  .
end

ここで、単に既存のテストコードを修正するだけではなく、新しいテストコードを追加した理由について考えてみてください (ヒント: 答えは3.3.1にあります)。

ここでテストスイートを実行して、テストが失敗することを確認します。

$ bundle exec rspec spec/requests/static_pages_spec.rb

テストがパスするためには、リスト4.5のようにHomeページのビューからprovideの行を削除する必要があります。

リスト4.5 ページタイトルをカスタマイズせずに表示する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>

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

$ bundle exec rspec spec/requests/static_pages_spec.rb

Rails開発経験者にとっては、リスト4.2のコードはスタイルシートをインクルードするのと大差ない単純なものですが、ここにもRuby未経験者を混乱させる可能性のある概念が多数含まれています。モジュール、コメント、ローカル変数割り当て、論理値 (boolean)、制御フロー、文字列の挿入、戻り値です。これらの概念についても、この章ですべて説明します。

4.2文字列(string)とメソッド

Ruby を学ぶためのツールとして、主にRailsコンソールを使用することにします。これは2.3.3 でも登場した、Railsアプリケーションを対話的に操作するためのコマンドラインツールです。コンソールはインタラクティブRuby (irb) 上に構築されているため、Rubyの機能をすべて使うことができます (4.4.4でも説明しますが、コンソールからRails環境にアクセスすることもできます)。以下のコマンドをコマンドラインで実行し、Railsコンソールを起動しましょう。

$ rails console
Loading development environment
>>

デフォルトでは、コンソールは開発 (development) 環境という、Railsによって定義された3種類の環境のうちの1つで起動します (他の2つはテスト (test) 環境本番 (production) 環境です)。この区別はこの章においては重要ではありませんが、7.1.1でこれらの環境について詳細に説明します。

Railsコンソールは素晴しい学習ツールであり、その中を自由に探索できます。コンソールの中で何をしようとも、何かを壊すことは (まず) ありえないので、ご安心ください。Railsコンソールでは、スタックから抜けるにはCtrl-C を押し、完全にコンソールを終了するにはCtrl-Dを押します。以後この章を進めるにあたり、有用なリソースであるRuby APIを参照しながら学習することをぜひお勧めします。Ruby APIには高濃縮の情報が詰まっています (少々濃厚すぎるとも言えます)。たとえば、Rubyの文字列の詳細を知りたい場合は、Ruby APIエントリのStringクラスを参照すればよいのです。

4.2.1コメント

Rubyのコメント はナンバー記号# ("ハッシュマーク"や、"オクトソープ"とも呼ばれます) から始まり、行の終わりまでコメントとして扱われます。Rubyはコメントの内容を実行することはありませんが、適切なコメントはそれを読む人間にとって (コードの作者にとっても) 非常に有用です。以下のコードの場合、

  # ページごとの完全なタイトルを返します。
  def full_title(page_title)
    .
    .
    .
  end

最初の行が、その後に定義されている関数の目的を説明しているコメントです。

コンソール内ではコメントを使わないのが普通ですが、ここでは学習のためにあえて以下のようにコメントを追加してみましょう。

$ rails console
>> 17 + 42   # 整数の加算
=> 59

この章のコードを (ファイルに保存するのでなく) Railsコンソールに入力したりコピペしたりするときであれば、コメントを省略してもかまいません。コメントをRailsコンソールに入力しても、コメントは常に無視されるので問題ありません。

4.2.2文字列

文字列 (string)は、おそらくどのWeb アプリケーションにおいても最も重要なデータ構造です。これは、Web ページというものが究極的にはサーバーからブラウザに送信された文字列にすぎないためです。それでは、コンソールで文字列について調べてみましょう。今回はrails consoleの代わりに、短縮版のrails cでコンソールを起動します。

$ rails c
>> ""         # 空の文字列
=> ""
>> "foo"      # 空でない文字列
=> "foo"

ここで入力したものは文字列リテラルと呼ばれ (面白いことにリテラル文字列とも呼ばれます)、ダブルクォート" で囲むことで作成できます。このコンソールは、入力したそれぞれの行を評価した結果を表示しており、文字列リテラルの場合には文字列自身が表示されます。

+ 演算子を使用して、文字列を結合することもできます。

>> "foo" + "bar"    # 文字列の結合
=> "foobar"

評価の結果は、"foo""bar" を足した"foobar"になりました2

文字列を組み立てる他の方法として式展開 (interpolation)というものがあり、#{}という特殊な構文を使用します3

>> first_name = "Michael"    #変数割り当て
=> "Michael"
>> "#{first_name} Hartl"     # 文字列の式展開
=> "Michael Hartl"

ここでは、"Michael" という値をfirst_name変数に割り当て、この変数が "#{first_name} Hartl" という文字列の中で式展開されます。苗字と名前の両方を変数に割り当てることもできます。

>> first_name = "Michael"
=> "Michael"
>> last_name = "Hartl"
=> "Hartl"
>> first_name + " " + last_name    # スペースをはさんで結合する
=> "Michael Hartl"
>> "#{first_name} #{last_name}"    # 式展開による同等のコード
=> "Michael Hartl"

最後の2つの結果は同等であることに注目してください。なお、著者は後者の式展開が好みです。空白を" "という形式で加えるのはぎこちなく思えます。

出力

文字列を出力するために、Rubyの関数で最も一般に使われるのはputsです (putの三人称単数現在形ではなく “put string”なので、“put ess” と発音します)。

>> puts "foo"     # put string
foo
=> nil

puts メソッドは副作用が重要な役割を果たします。どういうことかと言うと、puts "foo"は文字列 foo を副作用としてスクリーンに表示しますが、返り値には「無」を返します (正確にはnilというリテラルを返します。nil は "まったく無い"ことを表すRubyの特別な値です)。(=> nil という結果は、今後簡素化のために省略することがあります。)

putsを使用して出力すると、改行文字である\nが自動的に出力の末尾に追加されます。printメソッドも同様の出力を行いますが、以下のように、改行文字を追加しない点が異なります。

>> print "foo"    # 文字列の出力 (putsと同じだが改行しない)
foo=> nil
>> print "foo\n"  # puts "foo" と同等
foo
=> nil

シングルクォート内の文字列

これまでの例ではすべてダブルクォート文字列を使用していましたが、Rubyではシングルクォートもサポートしています。ほとんどの場合、ダブルクォートとシングルクォートのどちらを使用しても実質的に同じです。

>> 'foo'          # シングルクォートで囲んだ文字列
=> "foo"
>> 'foo' + 'bar'
=> "foobar"

ただし、1つ重要な違いがあります。Rubyはシングルクォート文字列の中では式展開を行いません。

>> '#{foo} bar'     # シングルクォートで囲まれた文字列では式展開されない
=> "\#{foo} bar"

逆に言えば、ダブルクォート文字列を用いた文字列で#のような特殊な文字を使用する場合は、この文字をバックスラッシュでエスケープする必要があります。

ダブルクォート文字列でもシングルクォート文字列と同じことができ、ダブルクォート文字列では式展開もできるのであれば、シングルクォート文字列にはどのような使い道があるのでしょうか。シングルクォートは、入力した文字をエスケープせずに「そのまま」保持するときに便利です。たとえば、いわゆる “バックスラッシュ” 文字は、改行文字\nと同様多くのシステム上で特殊な文字として扱われます。シングルクォートで文字列を囲めば、簡単にバックスラッシュ文字のような特殊文字をそのまま変数に含めることができます。

>> '\n'       # 'バックスラッシュ n' をそのまま扱う
=> "\\n"

以前の例で扱った#文字と同様、Rubyではバックスラッシュをエスケープするためにバックスラッシュがもう1つ必要です。ダブルクォート文字列の中では、バックスラッシュ文字そのものは2つのバックスラッシュによって表されます。このようなささいな例の場合はそれほど問題になりませんが、以下のようにエスケープの必要な文字が大量にある場合には、シングルクォートは非常に便利です。

>> 'Newlines (\n) and tabs (\t) both use the backslash character \.'
=> "Newlines (\\n) and tabs (\\t) both use the backslash character \\."

4.2.3オブジェクトとメッセージ受け渡し

Rubyでは、あらゆるものがオブジェクトです。文字列やnilですらオブジェクトです。このことの技術的な意味は4.4.2で説明しますが、オブジェクトについてのこのような定義は、本で読んだだけではわかりません。さまざまなオブジェクトの例に触れることで、オブジェクトとは何であるかという直感を時間をかけて養う必要があります。

逆に、オブジェクトが何をするかを説明するのは簡単です。オブジェクトとは (いついかなる場合にも) メッセージに応答するものです。文字列のようなオブジェクトは、たとえば lengthというメッセージに応答できますが、これは文字列の文字数を返します。

>> "foobar".length        # 文字列に "length" というメッセージを渡す
=> 6

オブジェクトに渡されるメッセージは、一般にはメソッドと呼ばれます。メソッドの実体は、そのオブジェクトに定義された関数です4。Rubyの文字列は、以下のようにempty?メソッドにも応答することができます。

>> "foobar".empty?
=> false
>> "".empty?
=> true

empty?メソッドの末尾にある疑問符に注目してください。これは Rubyの慣習で、true もしくは falseという論理値 (boolean)を返すことを示します。論理値は特に制御フローにおいて有用です。

>> s = "foobar"
>> if s.empty?
>>   "The string is empty"
>> else
>>   "The string is nonempty"
>> end
=> "The string is nonempty"

論理値は、&& (“and”)、|| (“or”)、そして! ("not") 演算子によって結合することもできます。

>> x = "foo"
=> "foo"
>> y = ""
=> ""
>> puts "Both strings are empty" if x.empty? && y.empty?
=> nil
>> puts "One of the strings is empty" if x.empty? || y.empty?
"One of the strings is empty"
=> nil
>> puts "x is not empty" if !x.empty?
"x is not empty"
=> nil

Rubyでは、あらゆるものがオブジェクトです。従って、nilもオブジェクトであり、これも多くのメソッドに応答できます。ほぼすべてのオブジェクトを文字列に変換するto_sメソッドを使用して、nilがメソッドに応答する例をお目にかけましょう。

>> nil.to_s
=> ""

確かに空文字列が出力されました。今度はnil に対してメッセージを連鎖 (chain)して渡せることを確認します。

>> nil.empty?
NoMethodError: You have a nil object when you didn't expect it!
You might have expected an instance of Array.
The error occurred while evaluating nil.empty?
>> nil.to_s.empty?      # メッセージの連鎖
=> true

上に示したように、nil オブジェクトはempty? メソッドに応答しませんが、nil.to_sには応答します。

ご想像のとおり、nilかどうかを調べるメソッドもあります:

>> "foo".nil?
=> false
>> "".nil?
=> false
>> nil.nil?
=> true

ところで、以下のコードは

puts "x is not empty" if !x.empty?

ifキーワードの別の使い方を示しています。Rubyではこのように、後続するifでの条件式が真のときにだけ実行される式を書くことができ、コードが非常に簡潔になります。なお、unlessキーワードも同様に使用できます。

>> string = "foobar"
>> puts "The string '#{string}' is nonempty." unless string.empty?
The string 'foobar' is nonempty.
=> nil

Rubyにおいてnilは特別なオブジェクトです。Rubyのオブジェクトのうち、オブジェクトそのものの論理値がfalseになるのは、(falseというオブジェクト自身を除いて) nilだけです。

>> if nil
>>   true
>> else
>>   false        # nilはfalse
>> end
=> false

その他のあらゆるRuby のオブジェクトは、ゼロですらtrueです。

>> if 0
>>   true        # 0 (を含むあらゆるオブジェクト、nilとfalseは除く) はtrue
>> else
>>   false
>> end
=> true

4.2.4メソッドの定義

Railsコンソールでも、リスト3.6homeアクションや、リスト4.2full_titleヘルパーと同じ方法でメソッドを定義することができます (メソッドの定義はファイルで行うのが普通なので、コンソールで行うのは少々面倒ですが、デモンストーレション目的であれば十分です)。たとえば、引数を1つ取り、引数が空かどうかに基づいたメッセージを返すstring_messageという関数を定義してみましょう。

>> def string_message(string)
>>   if string.empty?
>>     "It's an empty string!"
>>   else
>>     "The string is nonempty."
>>   end
>> end
=> nil
>> puts string_message("")
It's an empty string!
>> puts string_message("foobar")
The string is nonempty.

ここで、Rubyの関数は 暗黙の戻り値を持つということに注意してください。これは、最後に評価された式の値が自動的に返されることを意味します。この場合、引数のstringが空かどうかに基づいた2つのメッセージ文字列のうちのいずれかが返されます。Rubyでは、戻り値を明示的に指定することもできます。以下の関数は上の関数と同じ結果を返します。

>> def string_message(string)
>>   return "It's an empty string!" if string.empty?
>>   return "The string is nonempty."
>> end

上の説明で気付いた方もいると思いますが、2番目のreturn は実はなくてもかまいません。関数中の最後の表現 "The string is nonempty." は、returnキーワードがあってもなくても値を返すためです。ここでは、両方にreturnを使用する方が見た目の対称性が保たれるので好ましいと言えます。

4.2.5 title ヘルパー、再び

ここまでの解説でRubyの知識がある程度身についたので5、リスト4.2full_titleヘルパーを理解できるようになったはずです。

module ApplicationHelper

  # ページごとの完全なタイトルを返します。# コメント行
  def full_title(page_title)                          # メソッド定義
    base_title = "Ruby on Rails Tutorial Sample App"  # 変数に値を割り当てる
    if page_title.empty?                              # 論理値テスト
      base_title                                      # 暗黙の返り値
    else
      "#{base_title} | #{page_title}"                 # 文字列の式展開
    end
  end
end

Webサイトのレイアウトで使用するコンパクトなヘルパーメソッドは、関数定義、変数割り当て、論理評価、制御フロー、そして文字列の式展開などのRubyの要素が一体となってできあがっています。最後にmodule ApplicationHelperという要素について解説します。このモジュールは、関連したメソッドをまとめる方法の1つで、Rubyのクラスにincludeを使うことでミックスイン (mixed in)することができます。通常のRubyを書くときには、モジュールを書いてはこれを明示的にインクルードするという作業を頻繁に行いますが、このヘルパーモジュールの場合はRailsが自動的にインクルードしてくれます。つまり、このfull_titleメソッドは自動的にすべてのビューで利用できます。

4.3他のデータ構造

Webアプリケーションは突き詰めればただの文字列に過ぎませんが、実際にはこれらの文字列を作るために文字列以外のデータ構造も必要となります。この節では、Railsアプリケーションを書くために重要となる、いくつかのRubyのデータ構造について説明します。

4.3.1配列と範囲演算子

配列 (array) は、特定の順序を持つ要素のリストです。本書Railsチュートリアルではこれまで配列について解説していませんでしたが、配列を理解することは、ハッシュ (4.3.3) やRailsのデータモデルを理解するための重要な基盤となります (データモデルとはhas_manyなどの関連付けのことであり、詳細については2.3.310.1.3で説明します)。

Rubyの文字列の理解にだいぶ時間を使ってしまいましたので、次に進むことにします。文字列から配列を得る自然な方法の1つとして、以下のsplitメソッドがあります。

>>  "foo bar     baz".split     # 文字列を3つの要素を持つ配列に分割する
=> ["foo", "bar", "baz"]

この操作によって、3つの文字列からなる配列が得られます。デフォルトでは、splitは文字列を空白で区切って配列にしますが、以下のように他の文字を指定して区切ることもできます。

>> "fooxbarxbazx".split('x')
=> ["foo", "bar", "baz"]

多くのコンピュータ言語の慣習と同様、 Rubyの配列もゼロオリジンを採用しています。これは、配列の最初の要素のインデックスは0、2番目は1...と続くことを意味します。

>> a = [42, 8, 17]
=> [42, 8, 17]
>> a[0]               # 配列へのアクセスには [ ] を使用する
=> 42
>> a[1]
=> 8
>> a[2]
=> 17
>> a[-1]              # 負の値を持つインデックスも使用できる
=> 17

上で示したとおり、配列の要素にアクセスするには角括弧を使用します。Rubyでは、角括弧によるアクセス方法以外にも配列の要素にアクセスする方法が提供されています6

>> a                  # 'a' の内容に注目
=> [42, 8, 17]
>> a.first
=> 42
>> a.second
=> 8
>> a.last
=> 17
>> a.last == a[-1]    # ==演算子による比較
=> true

最後の行では、等しいことを確認する比較演算子==を使ってみました。この演算子や != (“等しくない”) などの演算子は、他の多くの言語と共通です。

>> x = a.length       # 配列も文字列と同様 'length' メソッドに応答する
=> 3
>> x == 3
=> true
>> x == 1
=> false
>> x != 1
=> true
>> x >= 1
=> true
>> x < 1
=> false

配列は、上記コードの最初の行のlengthメソッド以外にも、さまざまなメソッドに応答します。

>> a
=> [42, 8, 17]
>> a.sort
=> [8, 17, 42]
>> a.reverse
=> [17, 8, 42]
>> a.shuffle
=> [17, 42, 8]
>> a
=> [42, 8, 17]

上のどのメソッドを実行した場合にも、a自身は変更されていないという点に注目してください。配列の内容を変更したい場合は、そのメソッドに対応する “破壊的” メソッドを使用します。

>> a
=> [42, 8, 17]
>> a.sort!
=> [8, 17, 42]
>> a
=> [8, 17, 42]

また、pushメソッド (または同等の<<演算子) を使用して配列に追加することもできます。

>> a.push(6)                  # 配列に6を追加
=> [42, 8, 17, 6]
>> a << 7                     # 配列に7を追加
=> [42, 8, 17, 6, 7]
>> a << "foo" << "bar"        # 配列に連続プッシュ
=> [42, 8, 17, 6, 7, "foo", "bar"]

最後の例では、要素の追加を連鎖 (chain) できることを示しました。また、Rubyの配列は、他の多くの言語の配列とは異なり、さまざまな型を共存させることができます (この場合は整数と文字列)。

文字列を配列に変換するのにsplitを使用しました。joinメソッドはこれと逆の動作です。

>> a
=> [42, 8, 17, 7, "foo", "bar"]
>> a.join                       # 区切り文字なしで連結
=> "428177foobar"
>> a.join(', ')                 # カンマとスペースで区切って連結
=> "42, 8, 17, 7, foo, bar"

範囲 (range)は、配列と密接に関係しています。to_aメソッドを使用して配列に変換すると理解しやすいと思います。

>> 0..9
=> 0..9
>> 0..9.to_a              # 配列でない数字の9にto_aを実行してしまった
NoMethodError: undefined method `to_a' for 9:Fixnum
>> (0..9).to_a            # 範囲にto_aを実行するには丸括弧で囲む
=> [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] 

0..9 は範囲として有効ですが、上の2番目の表記ではメソッドを呼ぶ際に括弧を追加する必要があることを示しています。

範囲は、配列の要素を取り出すのに便利です。

>> a = %w[foo bar baz quux]         # %wを使用して文字列の配列に変換
=> ["foo", "bar", "baz", "quux"]
>> a[0..2]
=> ["foo", "bar", "baz"]

インデックスに-1という値を指定できるのは極めて便利です。-1を使用すると、配列の長さを知らなくても配列の最後の要素を指定することができ、これにより配列を特定の開始位置の要素から最後の要素までを一度に選択することができます。

>> a = (0..9).to_a
=> [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] 
>> a[2..(a.length-1)]               # 配列の長さを明示的に使用している。
=> [2, 3, 4, 5, 6, 7, 8, 9]
>> a[2..-1]                         # index -1の使用例。
=> [2, 3, 4, 5, 6, 7, 8, 9]

以下のように、文字に対しても範囲を使用できます。

>> ('a'..'e').to_a
=> ["a", "b", "c", "d", "e"]

4.3.2ブロック

配列と範囲はいずれも、ブロックを伴うさまざまなメソッドに対して応答することができます。ブロックとは、Rubyの極めて強力な機能であり、かつ混乱を呼びやすい機能でもあります。

>> (1..5).each { |i| puts 2 * i }
2
4
6
8
10
=> 1..5

上のコードは、範囲オブジェクトである(1..5)に対してeachメソッドを呼び出します。そのときに、{ |i| puts 2 * i }というブロックを渡します。|i|では変数名が縦棒に囲まれていますが、これはブロック変数に対して使用するRubyの構文で、ブロックを操作するときに使用する変数を指定します。この場合、範囲オブジェクトのeachメソッドは、iという1つのローカル変数を使用してブロックを操作できます。そして、範囲に含まれるそれぞれの値をこの変数に次々に代入してブロックを実行します。

ブロックは波括弧 { } で囲んで示すことができますが、以下のようにdoとendで囲んで示すこともできます。

>> (1..5).each do |i|
?>   puts 2 * i
>> end
2
4
6
8
10
=> 1..5

ブロックには複数の行を記述できます (実際ほとんどの場合複数です)。Rails チュートリアルでは、波括弧を短い1行のブロックに使用し、do..end記法を長い1行や複数行のブロックに使用するという、Ruby共通の慣習に従っています。

>> (1..5).each do |number|
?>   puts 2 * number
>>   puts '--'
>> end
2
--
4
--
6
--
8
--
10
--
=> 1..5

ここではnumberiと差し替えたことで、他の変数名を使用してもよいということを強調しています。

ブロックは見た目に反して奥が深く、ブロックを十分に理解するためには相当なプログラミング経験が必要です。そのためには、ブロックを含むコードをたくさん読みこなすことでブロックの本質を会得する以外に方法はありません7。幸いなことに、人間には個別の事例を一般化する能力というものがあります。ささやかですが、mapメソッドなどを使用したブロックの使用例を参考のためにいくつか挙げてみます。

>> 3.times { puts "Betelgeuse!" }   # 3.timesではブロックに変数を使用していない
"Betelgeuse!"
"Betelgeuse!"
"Betelgeuse!"
=> 3
>> (1..5).map { |i| i**2 }          # **は累乗を表す
=> [1, 4, 9, 16, 25]
>> %w[a b c]                        # %wは文字列配列への変換であることを思い出そう
=> ["a", "b", "c"]
>> %w[a b c].map { |char| char.upcase }
=> ["A", "B", "C"]
>> %w[A B C].map { |char| char.downcase }
=> ["a", "b", "c"]

上に示したように、map メソッドは、配列や範囲オブジェクトの各要素に対して、与えられたブロックを適用した結果を返します。

ところで、1.4.4でランダムなサブドメインを生成するために以下のRubyコードを紹介しましたが、今なら理解できる状態になったはずです。

('a'..'z').to_a.shuffle[0..7].join

このコードを段階的に組み立ててみると、動作がよくわかります。

>> ('a'..'z').to_a                     # アルファベットの配列
=> ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o",
"p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z"]
>> ('a'..'z').to_a.shuffle             # シャッフルする
=> ["c", "g", "l", "k", "h", "z", "s", "i", "n", "d", "y", "u", "t", "j", "q",
"b", "r", "o", "f", "e", "w", "v", "m", "a", "x", "p"]
>> ('a'..'z').to_a.shuffle[0..7]       # 最初の8つの要素を取り出す
=> ["f", "w", "i", "a", "h", "p", "c", "x"]
>> ('a'..'z').to_a.shuffle[0..7].join  # つなげて1つの文字列にする
=> "mznpybuj"

4.3.3ハッシュとシンボル

ハッシュは、本質的には配列と同じですが、インデックスとして整数値以外のものも使用できる点が配列と異なります (この理由から、いくつかの言語 (特にPerl) ではハッシュを連想配列と呼ぶこともあります)。ハッシュのインデックス (キーと呼ぶのが普通です) は、通常何らかのオブジェクトです。たとえば、以下のように文字列をキーとして使用できます。

>> user = {}                          # {}は空のハッシュ
=> {}
>> user["first_name"] = "Michael"     # キー: "first_name"、値: "Michael"
=> "Michael"
>> user["last_name"] = "Hartl"        # キー: "last_name"、値: "Hartl"
=> "Hartl"
>> user["first_name"]                 # 配列と同じ方法で要素にアクセスできる
=> "Michael"
>> user                               # ハッシュのリテラルな表記
=> {"last_name"=>"Hartl", "first_name"=>"Michael"}

ハッシュは、キーと値のペアを波括弧で囲んで表記します。キーと値のペアを持たない括弧の組 ({}) は空のハッシュです。ここで重要なのは、ハッシュの波括弧は、ブロックの波括弧とはまったく別物であるという点です (これは確かに紛らわしいものです) 。ハッシュは配列と似ていますが、1つの重要な違いとして、ハッシュでは要素の「並び順」が保証されないという点があります8

ハッシュの要素を1つずつ角括弧を使って定義するよりも、キーと値をハッシュロケットと呼ばれる=> によってリテラル表現するほうが簡単です。

>> user = { "first_name" => "Michael", "last_name" => "Hartl" }
=> {"last_name"=>"Hartl", "first_name"=>"Michael"}

ここではRubyにおける慣習として、ハッシュの最初と最後に空白を追加しています。この空白はあってもなくてもよく、コンソールでは無視されます (なぜスペースを置くようになったのかはわかりません。おそらく初期の有力な Rubyプログラマが好んだ結果、慣習となったのでしょう)。

ここまではハッシュのキーとして文字列を使用していましたが、Railsでは文字列よりもシンボルを使用する方が普通です。シンボルは文字列と似ていますが、クォートで囲む代わりにコロンが前に置かれている点が異なります。たとえば、:nameはシンボルです。もちろん、余計なことを一切考えずに、シンボルを単なる文字列とみなしても構いません9

>> "name".split('')
=> ["n", "a", "m", "e"]
>> :name.split('')
NoMethodError: undefined method `split' for :name:Symbol
>> "foobar".reverse
=> "raboof"
>> :foobar.reverse
NoMethodError: undefined method `reverse' for :foobar:Symbol

シンボルは、Ruby以外ではごく一部の言語にしか採用されていない特殊なデータ形式です。最初は奇妙に思うかもしれませんが、Railsではシンボルをふんだんに使用しているので、すぐに慣れるでしょう。

ハッシュのキーとしてシンボルを採用する場合、user のハッシュは以下のように定義できます。

>> user = { :name => "Michael Hartl", :email => "michael@example.com" }
=> {:name=>"Michael Hartl", :email=>"michael@example.com"}
>> user[:name]              # :name に一致する値にアクセスします。
=> "Michael Hartl"
>> user[:password]          # 未定義のキーの値にアクセスします。
=> nil

最後の例を見ると、未定義のハッシュ値は単純にnilであることがわかります。

ハッシュではシンボルをキーとして使うことが一般的なので、Ruby 1.9ではこのような特殊な場合のための新しい記法をサポートしています。

>> h1 = { :name => "Michael Hartl", :email => "michael@example.com" }
=> {:name=>"Michael Hartl", :email=>"michael@example.com"}
>> h2 = { name: "Michael Hartl", email: "michael@example.com" }
=> {:name=>"Michael Hartl", :email=>"michael@example.com"}
>> h1 == h2
=> true

2つ目の記法は、シンボルとハッシュロケットの組み合わせを、以下のようにキーの名前の (前ではなく) 後にコロンを置き、その後に値が続くように置き換えたものです。

{ name: "Michael Hartl", email: "michael@example.com" }

この構成は、JavaScriptなど他の言語のハッシュ記法により近いものになっており、Railsコミュニティでも人気が高まっています。どちらの記法もよく使われているので、両方の見分けがつくことが重要です。

リスト4.6に示したように、ハッシュの値にはほぼ何でも使用することができ、他のハッシュを使用することすらできます。

リスト4.6 ハッシュをネストする。
>> params = {}        # 'params' というハッシュを定義する ('parameters' の略)。
=> {}
>> params[:user] = { name: "Michael Hartl", email: "mhartl@example.com" }
=> {:name=>"Michael Hartl", :email=>"mhartl@example.com"}
>> params
=> {:user=>{:name=>"Michael Hartl", :email=>"mhartl@example.com"}}
>>  params[:user][:email]
=> "mhartl@example.com"

Railsでは、このようなハッシュのハッシュ (またはネストされたハッシュ) が大量に使われています。実際の使用例は7.3で説明します。

配列や範囲オブジェクトと同様、ハッシュもeachメソッドに応答します。たとえば、:success:errorという 2つの状態を持つ flash という名前のハッシュについて考えてみましょう。

>> flash = { success: "It worked!", error: "It failed." }
=> {:success=>"It worked!", :error=>"It failed."}
>> flash.each do |key, value|
?>   puts "Key #{key.inspect} has value #{value.inspect}"
>> end
Key :success has value "It worked!"
Key :error has value "It failed."

ここで、配列のeachメソッドでは、ブロックの変数は1つだけですが、ハッシュのeachメソッドでは、ブロックの変数はキーの2つになっていることに注意してください。従って、 ハッシュに対してeachメソッドを実行すると、ハッシュの1つの「キーと値のペア」ごとに処理を繰り返します。

最後の例として、便利なinspectメソッドを紹介します。これは要求されたオブジェクトを表現する文字列を返します。

>> puts (1..5).to_a            # 配列を文字列として出力
1
2
3
4
5
>> puts (1..5).to_a.inspect    # 配列のリテラルを出力
[1, 2, 3, 4, 5]
>> puts :name, :name.inspect
name
:name
>> puts "It worked!", "It worked!".inspect
It worked!
"It worked!"

ところで、オブジェクトを表示するためにinspectとputsを使用することは非常によくあることなので、 両者を合わせたp関数というショートカットがあります。

>> p :name             # 'puts :name.inspect' と同等
:name

4.3.4 CSS、再び

それでは、もう一度リスト4.1に戻り、レイアウトに CSS (cascading style sheet) を追加する以下の行を見てみましょう。

<%= stylesheet_link_tag "application", media: "all",
                                       "data-turbolinks-track" => true %>

今なら、このコードを理解できるようになったはずです。4.1でも簡単に説明したとおり、Railsではスタイルシートを追加するための特別な関数を使用しています。

stylesheet_link_tag "application", media: "all",
                                   "data-turbolinks-track" => true

上のコードは、その特別な関数を呼んでいます。しかし、ここで不思議な点がいくつもあります。まず、丸括弧がないのが不思議です。実は、Ruby では丸括弧は使用してもしなくても構いません。以下の2つの行は同等です。

# 関数呼び出しの丸括弧は省略可能。
stylesheet_link_tag("application", media: "all",
                                   "data-turbolinks-track" => true)
stylesheet_link_tag "application", media: "all",
                                   "data-turbolinks-track" => true

次に、:media引数はハッシュのようですが、波括弧がない点が不思議です。実は、ハッシュが関数呼び出しの最後の引数である場合は、波括弧を省略できます。以下の2つの行は同等です。

# 最後の引数がハッシュの場合、波括弧は省略可能。
stylesheet_link_tag "application", { media: "all",
                                     "data-turbolinks-track" => true }
stylesheet_link_tag "application", media: "all",
                                   "data-turbolinks-track" => true

次に、data-turbolinks-trackにおけるキーと値のペアの表記が、旧式のハッシュロケット (=>) スタイルになっている点が不思議です。実は、以下のような新しいハッシュ記法を使用すると、

data-turbolinks-track: true

:data-turbolinks-trackというシンボルを作成しようとしますが、シンボルではハイフンを使用できないため、この記法は無効です。このため、以下のような旧式のハッシュロケット記法を使用するしかないのです。

"data-turbolinks-track" => true

最後に、Rubyが以下のようなコードを正常に実行できているのが不思議です。

stylesheet_link_tag "application", media: "all",
                                   "data-turbolinks-track" => true

上のコードには途中に改行が含まれているにもかかわらずです。実は、Rubyは改行の有無に左右されません10。著者が上のコードに改行を追加した理由は、コードの読みやすさを損なわないために1行あたり80文字までに制限していたためです11

従って、

stylesheet_link_tag "application", media: "all",
                                   "data-turbolinks-track" => true

上のコードではstylesheet_link_tag関数を2つの引数で呼んでいます。最初の引数である文字列は、スタイルシートへのパスを示しています。次の引数であるハッシュには2つの要素があり、最初の要素はメディアタイプを示し、次の要素はturbolinks機能をオンにしています (Turbolinksの詳細については、本書の次のドラフト版で説明します)。コードが<%= %>で囲まれていることによって、 このコードの実行結果はERbのテンプレートに挿入されます。ブラウザ上でこのページのソースを表示すると、必要なスタイルシートが含まれていることを確認できます (リスト4.7)。(CSSファイル名の後に、?body=1のような行が余分に表示されていることがあります。これらはRailsによって挿入されているもので、サーバー上で変更があった場合にブラウザがCSSを再読み込みするのに使用します。)

リスト4.7 インクルードされたCSSによって生成されたHTMLソース。
<link data-turbolinks-track="true" href="/assets/application.css" media="all"
rel="stylesheet" />

実際にhttp://localhost:3000/assets/application.cssのCSSファイルにアクセスしてみると、わずかなコメントがある以外は空になっています。CSSの変更は第5章で行います。

4.4 Ruby におけるクラス

Rubyではあらゆるものがオブジェクトであるということは既に説明しましたが、この節では実際にオブジェクトをいくつか定義してみましょう。Rubyは、多くのオブジェクト指向言語と同様、メソッドをまとめるのにクラスを使用しています。これらのクラスからインスタンスが生成されることでオブジェクトが作成されます。オブジェクト指向プログラミングの経験がない方にとっては何のことだかわからないと思いますので、いくつかの具体例を示すことにします。

4.4.1コンストラクタ

実は、これまで示した多くの例の中でも、クラスを使用してオブジェクトのインスタンスを作成してきたのですが、オブジェクトを作成するところを明示的に説明していませんでした。たとえば、ダブルクォートを使って文字列のインスタンスを作成しましたが、これは文字列のオブジェクトを暗黙で作成するリテラルコンストラクタです。

>> s = "foobar"       # ダブルクォートを使用した、文字列のリテラルコンストラクタ
=> "foobar"
>> s.class
=> String

上のコードでは、文字列がclassメソッドに応答しており、その文字列が所属するクラスを単に返していることがわかります。

暗黙のリテラルコンストラクタを使う代わりに、明示的に同等の名前付きコンストラクタを使うことができます。名前付きコンストラクタは、クラス名に対してnewメソッドを呼び出します12

>> s = String.new("foobar")   # 文字列の名前付きコンストラクタ
=> "foobar"
>> s.class
=> String
>> s == "foobar"
=> true

この動作はリテラルコンストラクタと同等ですが、動作の内容が明確に示されています。

配列でも、文字列と同様にインスタンスを生成できます。

>> a = Array.new([1, 3, 2])
=> [1, 3, 2]

ただし、ハッシュの場合は若干異なります。配列のコンストラクタであるArray.new は配列の初期値を引数に取りますが、 Hash.new はハッシュのデフォルト 値を引数に取ります。これは、キーが存在しない場合のデフォルト値です。

>> h = Hash.new
=> {}
>> h[:foo]            # 存在しないキー :fooの値にわざとアクセスしてみる
=> nil
>> h = Hash.new(0)    # キーが存在しない場合のデフォルト値をnilから0に変更する
=> {}
>> h[:foo]
=> 0

メソッドがクラス自身 (この場合はnew) に対して呼び出されるとき、このメソッドをクラスメソッドと呼びます。クラスのnewメソッドを呼び出した結果は、そのクラスのオブジェクトであり、これはクラスのインスタンスとも呼ばれます。lengthのように、インスタンスに対して呼び出すメソッドはインスタンスメソッドと呼ばれます。

4.4.2クラス継承

クラスについて学ぶとき、superclassメソッドを使ってクラス階層を調べてみるとよくわかります。

>> s = String.new("foobar")
=> "foobar"
>> s.class                        # sのクラスを表示
=> String
>> s.class.superclass             # Stringクラスのスーパークラスを表示
=> Object
>> s.class.superclass.superclass  # Ruby 1.9では新たに BasicObject を使用
=> BasicObject
>> s.class.superclass.superclass.superclass
=> nil

継承階層を図4.1に示します。ここでは、StringクラスのスーパークラスはObjectクラスで、ObjectクラスのスーパークラスはBasicObjectクラスですが、 BasicObjectクラスはスーパークラスを持たないことがわかります。この図式は、すべての Ruby のオブジェクトにおいて成り立ちます。クラス階層をたどっていくと、 Rubyにおけるすべてのクラスは最終的にスーパークラスを持たないBasicObjectクラスを継承しています。これが、"Rubyではあらゆるものがオブジェクトである" ということの技術的な意味です。

string_inheritance_ruby_1_9
図4.1 Stringクラスの継承階層

クラスについてもっと深く知るためには、自分自身で作ってみるのが一番です。そこで、Wordクラスを作成し、その中に、ある単語を前からと後ろからのどちらから読んでも同じ (つまり回文になっている) ならばtrueを返すpalindrome?メソッドを作成してみましょう。

>> class Word
>>   def palindrome?(string)
>>     string == string.reverse
>>   end
>> end
=> nil

このクラスとメソッドは以下のように使うことができます。

>> w = Word.new              # Wordオブジェクトを作成する
=> #<Word:0x22d0b20>
>> w.palindrome?("foobar")
=> false
>> w.palindrome?("level")
=> true

もし上の例が少し不自然に思えるならば、勘が鋭いといえます。というのも、これはわざと不自然に書いたからです。文字列を引数に取るメソッドを作るためだけに、わざわざ新しいクラスを作るのは変です。単語文字列なので、リスト4.8のようにWordクラスは Stringクラスを継承するのが自然です (以下のリストを入力する前に、古いWordクラスの定義を消去するために、Railsコンソールをいったん終了してください)。

リスト4.8 コンソールでWordクラスを定義する。
>> class Word < String             # WordクラスはStringクラスを継承する。
>>   # 文字列が鏡文字であればtrueを返す。
>>   def palindrome?
>>     self == self.reverse        # selfは文字列自身を表す。
>>   end
>> end
=> nil

3.1.2でも簡単に説明しましたが、上のコードのWord < Stringは継承のためのRubyの記法です。こうすることで、新しいpalindrome?メソッドだけではなく、Stringクラスで使用できるすべてのメソッドをWordクラスに対しても使用できるようになります。

>> s = Word.new("level")    # 新しいWordを作成し、"level" で初期化する
=> "level"
>> s.palindrome?            # Wordが鏡文字かどうかmethod.
=> true
>> s.length                 # Wordがstringのすべてのメソッドも継承している
=> 5

WordクラスはStringクラスを継承しているので、コンソールを使用してクラス階層を明示的に確認できます。

>> s.class
=> Word
>> s.class.superclass
=> String
>> s.class.superclass.superclass
=> Object

図4.2にこのクラス階層を示します。

word_inheritance_ruby_1_9
図4.2リスト4.8の (組み込みではない) Wordクラスの継承階層

リスト4.8では、単語の文字を逆順にしたものが元の単語と同じであるかどうかのチェックを、Wordクラスの中から自分自身が持つ単語にアクセスすることで行なっていることに注目してください。Rubyでは、selfキーワードを使用してこれを指定することができます。Wordクラスの中では、selfはオブジェクト自身を指します。これはつまり、以下のコードを使用して、

self == self.reverse

単語が回文であるかどうかを確認できるということです13

4.4.3組込みクラスの変更

継承は強力な概念ですが、もし仮に継承を使用せずにpalindrome?メソッドをStringクラス自身に追加する (つまりStringクラスを拡張する) という、より自然な方法を使用することが可能だとしたら、わざわざWordクラスを作らなくてもpalindrome?をリテラル文字列に対して直接実行できるようになるはずです。そんなことが可能なのでしょうか (なお、現在のコードはそのようになっていないため、以下のようにエラーになります)。

>> "level".palindrome?
NoMethodError: undefined method `palindrome?' for "level":String

驚いたことに、Rubyでは組み込みの基本クラスの拡張が可能なのです。Ruby のクラスはオープンで変更可能であり、クラス設計者でない開発者でもこれらのクラスにメソッドを自由に追加することが許されています14

>> class String
>>   # 文字列が回文であればtrueを返す
>>   def palindrome?
>>     self == self.reverse
>>   end
>> end
=> nil
>> "deified".palindrome?
=> true

(Rubyで組み込みクラスにメソッドを追加できるということは実にクールですが、"deified" (=神格化された) という単語が回文になっていることも、それに劣らずクールではないでしょうか。)

組み込みクラスの変更は極めて強力なテクニックですが、大いなる力には大いなる責任が伴います (訳注: 「スパイダーマン」の名台詞)。従って、真に正当な理由がない限り、組み込みクラスにメソッドを追加することは無作法であると考えられています。Railsの場合、組み込みクラスの変更を正当化できる理由がいくつもあります。たとえば、Web アプリケーションでは、変数が絶対に空白にならないようにしたくなることがよくあります (ユーザー名などはスペースやその他の空白文字になって欲しくないものです) ので、Railsはblank?メソッドをRuby に追加しています。Railsの拡張は自動的にRailsコンソールにも取り込まれるので、以下のようにコンソールで拡張の結果を確認できます (注意: 以下のコードは純粋な irb では動作しません)。

>> "".blank?
=> true
>> "      ".empty?
=> false
>> "      ".blank?
=> true
>> nil.blank?
=> true

スペースが集まってできた文字列は空 (empty) とは認識されませんが、空白 (blank) であると認識されていることがわかります。ここで、nilは空白と認識されることに注意してください。nilは文字列ではないので、Railsが実はblank?メソッドをStringクラスではなく、そのさらに上の基底クラスに追加していることが推測できます。その基底クラスとは、(この章の最初で説明した) Object自身です。RailsによってRubyの組み込みクラスに追加が行われている例については、8.2.1で説明します。

4.4.4コントローラクラス

これまでクラスや継承について説明してきましたが、これらの話は前の章にもあったような気がします。それもそのはずで、StaticPagesコントローラで継承やクラスについて触れたことがありました (リスト3.16)。

class StaticPagesController < ApplicationController

  def home
  end

  def help
  end

  def about
  end
end

本書をここまで進めてきた今であれば、上のコードの意味は (たとえ漠然とであっても) 理解できるはずです。StaticPagesControllerクラスはApplicationControllerを継承しており、homeメソッド、helpメソッド、aboutメソッドを備えています。 Rails コンソールは、セッションごとにローカルのRails環境を読み込むので、コンソール内で明示的にコントローラを作成したり、そのクラス階層を調べたりすることができます15

>> controller = StaticPagesController.new
=> #<StaticPagesController:0x22855d0>
>> controller.class
=> StaticPagesController
>> controller.class.superclass
=> ApplicationController
>> controller.class.superclass.superclass
=> ActionController::Base
>> controller.class.superclass.superclass.superclass
=> ActionController::Metal
>> controller.class.superclass.superclass.superclass.superclass
=> AbstractController::Base
>> controller.class.superclass.superclass.superclass.superclass.superclass
=> Object

継承の関係を図4.3に示します。

static_pages_controller_inheritance
図4.3 StaticPagesコントローラの継承階層。

Railsコンソールでは、その中からコントローラのアクション (実はメソッド) を呼ぶこともできます。

>> controller.home
=> nil

ここでは、homeアクションの中身は空なのでnilが返されます。

ここで重要な点があります。Railsのアクションには戻り値がありません。少なくとも、返される値は重要ではありません。第3章で示したとおり、homeアクションはWebページを表示するためのものであり、値を返すためのものではありませんでした。しかも、第3章では一度もStaticPagesController.newを実行しませんでした。どうしてこれでうまくいっているのでしょうか。

実は、Railsは確かにRubyで書かれていますが、既にRubyとは別物なのです。Railsのクラスは、普通のRubyオブジェクトと同様に振る舞うものもありますが、多くのクラスにはRailsの魔法の粉が振りかけられています。Railsは独特であり、 Rubyとは切り離して学習する必要があります。このため、とにかくWebアプリケーションを書けるようになりたい方は、最初にRailsを学び、次にRubyを学んでから再びRailsに戻ってくることをお勧めします。

4.4.5ユーザークラス

最後に完全なクラスを作成して、この章を終わりにしましょう。そこで、第6章で使用するUserクラスを最初から作成することにします。

これまではコンソール上でクラスを定義しましたが、このような面倒な作業はもう行いたくありません。これからは、アプリケーションのルートディレクトリにexample_user.rbファイルを作成し、そこにリスト4.9のように書くことにします。

リスト4.9 example_userで使用するコード。
example_user.rb
class User
  attr_accessor :name, :email

  def initialize(attributes = {})
    @name  = attributes[:name]
    @email = attributes[:email]
  end

  def formatted_email
    "#{@name} <#{@email}>"
  end
end

上のコードはこれまでよりもやや複雑になっていますので、順に見ていくことにします。以下の最初の行は、

  attr_accessor :name, :email

ユーザー名とメールアドレスに対応するアトリビュートアクセサをそれぞれ作成します。このコードは、2.2.2でも説明したように、@nameおよび@emailインスタンス変数について、取り出し(get) と割り当て(set) を行う "ゲッター" と "セッター" というメソッドをそれぞれ作成します。Railsでは、インスタンス変数を作成するだけでビューで自動的に使えるようになるという点に主な利用価値がありますが、一般的には、インスタンス変数はRubyのそのクラス内のどこでも利用できるようにしたい変数として使われます (これについては後で詳しく説明します)。インスタンス変数は常に@記号で始まり、未定義の状態では値がnilになります。

最初の行にあるinitializeは、Rubyの特殊なメソッドです。これは User.newを実行すると自動的に呼び出されるメソッドです。この場合のinitializeメソッドは、以下のようにattributesという引数を1つ取ります。

  def initialize(attributes = {})
    @name  = attributes[:name]
    @email = attributes[:email]
  end

上のコードで、attributes変数は空のハッシュをデフォルトの値として持つため、名前やメールアドレスのないユーザーを作ることができます (4.3.3を思い出してください。存在しないキーに対してハッシュはnilを返すので、:nameキーがなければattributes[:name]nil になり、同じことがattributes[:email]にも言えます)。

最後に、formatted_emailメソッドを定義しましょう (4.2.2)。このメソッドは、文字列の式展開を利用して、@name@emailに割り当てられた値をユーザーのメールアドレスとして構成します。

  def formatted_email
    "#{@name} <#{@email}>"
  end

@ 記号によって示されているとおり、@name@emailは両方ともインスタンス変数なので、自動的にformatted_emailメソッドで使えるようになります。

Railsコンソールを起動し、example_userのコードをrequireして、自作したクラスを試しに使ってみましょう。

>> require './example_user'     # example_user のコードを読み込む方法
=> ["User"]
>> example = User.new
=> #<User:0x224ceec @email=nil, @name=nil>
>> example.name                 # attributes[:name]がnilなのでnilが返される
=> nil
>> example.name = "Example User"           # nil でない名前を代入
=> "Example User"
>> example.email = "user@example.com"      # nil でないメールアドレスを代入
=> "user@example.com"
>> example.formatted_email
=> "Example User <user@example.com>"

上のコードで、requireのパスにある’.’は、Unixの “カレントディレクトリ” (現在のディレクトリ) を表し、’./example_user’というパスは、カレントディレクトリからの相対パスでexample_userファイルを探すようにRubyに指示します。次のコードでは空のexample_userを作成します。次に、対応する属性にそれぞれ手動で値を代入することで、名前とメールアドレスを与えます (リスト4.9attr_accessorを使用しているので、アトリビュートアクセサを使用して代入できます)。以下のコードは、

example.name = "Example User"

@name変数に"Example User"という値を設定します。同様にemail属性にも値を設定します。これらの値はformatted_emailメソッドで使用されます。

4.3.4では、最後のハッシュ引数の波括弧を省略できることを説明しました。それと同じ要領でinitializeメソッドにハッシュを渡すことで、属性が定義済みの他のユーザーを作成することができます。

>> user = User.new(name: "Michael Hartl", email: "mhartl@example.com")
=> #<User:0x225167c @email="mhartl@example.com",
@name="Michael Hartl">
>> user.formatted_email
=> "Michael Hartl <mhartl@example.com>"

第7章 では、 ハッシュ引数を使用してオブジェクトを初期化します。これは一般にマスアサインメント (mass assignment) と呼ばれる技法で、Railsアプリケーションで多用されています。

4.5最後に

以上で、Ruby言語の概要の説明を終わります。第5章では、この章で学んだ内容をサンプルアプリケーションの開発に活かしていきます。

4.4.5で作成したexample_user.rbファイルは今後使用することはありませんので、削除してください。

$ rm example_user.rb

その他の変更はリポジトリにコミットしましょう。

$ git add .
$ git commit -m "Add a full_title helper"

4.6演習

  1. リスト4.10のコードにある2つの疑問符を、それぞれ適切なメソッドに置き換えて、与えられた文字列の文字をシャッフルする関数を作成してください。ヒント: splitメソッド、shuffleメソッド、joinメソッドを組み合わせてみましょう。
  2. リスト4.11を参考にして、上で作成したshuffleメソッドを Stringクラスに追加してください。
  3. person1person2person3という3つのハッシュを作成してください。それぞれのハッシュには:firstキーと:lastキーを与え、さらにそれぞれのキーに名前と名字を値として割り当ててください。次にparamsハッシュを作成し、params[:father]person1params[:mother]person2、そしてparams[:child]person3になるようにしてください。最後に、params[:father][:first]などが正しい値を持っていることを確認してください。
  4. Ruby API のオンラインマニュアルを見つけて、Hashクラスのmergeメソッドについて読んでみてください。
  5. (自由課題) Ruby Koans16 (訳注: TDDでRubyを学べる無料のコンテンツ) をやってみて、Rubyの感覚を掴んでみてください。
リスト4.10 文字列をシャッフルする関数の骨組み。
>> def string_shuffle(s)
>>   s.split('').?.?
>> end
=> nil
>> string_shuffle("foobar")
リスト4.11 shuffleメソッドをStringクラスに追加するための骨組み。
>> class String
>>   def shuffle
>>     self.split('').?.?
>>   end
>> end
=> nil
>> "foobar".shuffle
  1. あるヘルパーが特定のコントローラでのみ使用するものであれば、それに対応するヘルパーファイルに置く必要があります。たとえばStaticPagesコントローラ用ヘルパーは、通常app/helpers/static_pages_helper.rbになります。今回の場合、full_titleヘルパーはサイトのすべてのページで使用することを前提にしていますが、Railsにはこのような場合のための特別なヘルパーファイルapp/helpers/application_helper.rbがあります。
  2. “foo”と“bar”という言葉の起源の詳細については、Jargon Fileの “foo” の項を参照してください。特に“foobar”は“FUBAR” (=めちゃくちゃ) とは何の関係もないということについて。
  3. PerlやPHPに精通した開発者であれば、"foo $bar"のようなドル記号による自動的な挿入を連想して比較することでしょう。
  4. この章の全体にわたって、関数という言葉とメソッドという言葉が混在していることを前もってお詫びいたします。Rubyでは関数とメソッドには何の違いもありません。すべてのメソッドは関数であり、すべての関数はメソッドでもあります。それもこれも、あらゆるものがオブジェクトであるからです。
  5. とはいうものの、まだ理解のおよばない点が1つ残っています。Railsがこれらすべてを結びつけている方法です。URLがどのようにしてアクションにマップされているのか、full_titleヘルパーをどのようにしてビューで使用できるようにしているのかなどは、この時点では明かされていません。これは実に興味深いテーマであり、意欲のある方はぜひ自分で調べてみてください。ただし、Railsがどのように動いているかを正確に知らなくても、Railsを使用するうえでは何の問題もありません (この点を深く理解したい方には、「The Rails 4 Way」(Obie Fernandez著) がお勧めです) 。
  6. このコードで使用しているsecondメソッドは、実はRuby自身の一部ではなく、Railsが追加したものです。このコードが動作するのは、RailsによるRubyの拡張がRailsコンソールによって自動的に反映されるからです。
  7. なおエキスパート向けには、ブロックがクロージャになっているということを知っていただくと理解しやすいと思います。クロージャとは、データを伴う、その場限りの無名関数です。
  8. 実はRuby 1.9では、ハッシュの要素の順序が入力順と同じであることを保証していますが、ハッシュを特定の順序に依存してカウントするのは得策ではありません。
  9. 余計なものを削ぎ落した結果、シンボル同士の比較を容易に行えます。文字列は1文字ずつ比較する必要がありますが、シンボルは一度に全体を比較できます。これはハッシュのキーとして理想的な性質です。
  10. 改行は、行の末尾と次の行の始まりを示します。コードの中では、\nの文字列で表します。
  11. もちろん、人間がいちいち文字数を数えていたら頭がどうにかなってしまいます。だからこそ、多くのテキストエディタにはこれらを支援する機能が備わっています。たとえば、図1.1をもう一度見てみると、コードを80文字以下に抑えるための小さな縦線が右側に見えます (実際には少し余裕を持たせて78列にしてあります) 。TextMateを使用していれば、View > Wrap Column > 78で設定できます。Sublime Textを使用していれば、View > Ruler > 78、またはView > Ruler > 80で設定できます。
  12. このメソッドの動作は、使用しているRubyのバージョンによって異なる可能性があります。この例ではRuby 1.9.3を前提としています。
  13. Rubyのクラスやselfキーワードについての詳細は、RailsTipsの “Ruby におけるクラスとインスタンス変数” (英語) を参照してください。
  14. JavaScriptに精通している方のために補足すると、この機能は組み込みクラスのプロトタイプオブジェクトを使用してクラスを拡張することと似ています (読者のErik Eldridgeによる指摘に感謝します)。 
  15. これらの階層にあるクラスの詳細を知る必要はないと思います。私ですらそれらのクラスの詳細について知らないことがたくさんありますし、それでも私は2005年からRuby on Railsで問題なくプログラミングできています。これは (a) 私がよほど無能であるか、(b) Railsの内部を知りつくさなくても熟練したRails開発者になれる、ということのどちらかでしょう。私のためにも読者の皆様のためにも、後者であることを祈ります。
  16. http://rubykoans.com/ 

第5章レイアウトを作成する

第4章の簡単なRubyツアーの中で、サンプルアプリケーションにアプリケーションスタイルシートを含める方法を学びました。しかし、4.3.4で指摘したとおり、このスタイルシートは空のままです。この章では、アプリケーションにBootstrapフレームワークを組み込み、そして、カスタムスタイルを追加します1。また5.1までに、作成するページ (HomeやAboutなど) へのリンクをレイアウトに追加します。その途中で、パーシャル、Railsのルーティング、Asset Pipelineについて学び、さらにSassについても紹介します(5.2)。 また、第3章 で作成したテストを、最新のRSpecテクニックを用いてリファクタリングします。章の最後に、ユーザーをサイトにログインさせるための重要な一歩を踏み出します。

5.1構造を追加する

RailsチュートリアルはWeb開発のための本であり、Webデザインの本ではありませんが、だからといって何のスタイルもない寒々しい外観のアプリケーションでいつまでも作業を続けていると憂鬱になってしまいます。そこで、この章ではレイアウトにいくつかの構造とCSSを与えて最小限のスタイルを追加します。カスタムCSSルールの他に、Twitter社によるオープンソースのWebデザインフレームワークであるBootstrapを利用します。また、コードそのものにもスタイルを与えます。つまり、散らかりはじめたコードレイアウトをパーシャルを使用して整えるということです。

Webアプリケーションを作成するときに、ユーザーインターフェイスの概要をできるだけ早いうちに把握しておくことがしばしば有用です。本書の残りでは、モックアップ (Webの文脈ではよく ワイヤーフレームと呼ばれます) という、最終的なアプリケーションの外観を示す一種のラフスケッチを使用することにします2。この章では、 主に3.1で紹介したサイトロゴ、ナビゲーションヘッダー、サイトフッターを含む静的ページを開発します。これらのページの中で最も重要な、Homeページのモックアップを図5.1に示します。モックアップに基いて作成した最終結果は図5.7で確認することができます。両者を見比べると、細部が若干異なることに気が付くでしょう (たとえば、実際には最後にRailsのロゴをページに追加します)。しかしモックアップは正確である必要はありませんので、これで十分です。

home_page_mockup_bootstrap
図5.1サンプルアプリケーションのHomeページのモックアップ。(拡大)

Gitでバージョン管理をしているのであれば、これまでと同様、この時点で新しいブランチを作成するのがよいでしょう。

$ git checkout -b filling-in-layout

5.1.1ナビゲーション

第一段階として、サンプルアプリケーションにリンクとスタイルを追加するために、サイトのレイアウトファイルapplication.html.erb (リスト4.3で登場) にHTML構造を追加し、レイアウトファイルを更新します。この更新には、領域 (divタグ) の追加、CSSクラスの追加、サイトナビゲーションの起点となる領域の追加も含まれます。完全なファイルをリスト5.1に示します。続いて、これを構成している多くの部品について解説します。表示結果を今すぐ確認したいのであれば、図5.2で確認できます (注:この時点ではわざわざ見に行くほどの仕上がりではありませんが)。

リスト5.1 構造を追加したWebサイトのレイアウト。
app/views/layouts/application.html.erb
<!DOCTYPE html>
<html>
  <head>
    <title><%= full_title(yield(:title)) %></title>
    <%= stylesheet_link_tag "application", media: "all",
                                           "data-turbolinks-track" => true %>
    <%= javascript_include_tag "application", "data-turbolinks-track" => true %>
    <%= csrf_meta_tags %>
    <!--[if lt IE 9]>
    <script src="http://html5shim.googlecode.com/svn/trunk/html5.js"></script>
    <![endif]-->
  </head>
  <body>
    <header class="navbar navbar-fixed-top navbar-inverse">
      <div class="navbar-inner">
        <div class="container">
          <%= link_to "sample app", '#', id: "logo" %>
          <nav>
            <ul class="nav pull-right">
              <li><%= link_to "Home",    '#' %></li>
              <li><%= link_to "Help",    '#' %></li>
              <li><%= link_to "Sign in", '#' %></li>
            </ul>
          </nav>
        </div>
      </div>
    </header>
    <div class="container">
      <%= yield %>
    </div>
  </body>
</html>

それでは、リスト5.1の新しい要素を上から順に見ていきましょう。3.3.2でも簡単に説明しましたが、Rails 4はデフォルトでHTML5を使用します (<!DOCTYPE html>というdoctypeでそのことが示されています)。HTML5は比較的新しく、一部のブラウザ (特に旧式のInternet Explorer) ではHTML5のサポートが不完全であるため、以下のようなJavaScriptコード (通称 “HTML5 shim”) を使用してこの問題を回避します。

<!--[if lt IE 9]>
<script src="http://html5shim.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->

上のコードには、以下のような奇妙な構文が含まれています。

<!--[if lt IE 9]>

これは、Microsoft Internet Explorer (IE) のバージョンが9より小さい場合 (if lt IE 9) にのみ、囲まれている行を実行します。この風変わりな文法 [if lt IE 9] は、Railsの一部ではありません。これは実は、条件付きコメントと呼ばれるもので、今回のような状況のためにInternet Explorerで特別にサポートされています。これにより、Firefox、Chrome、Safariなどの他のブラウザに影響を与えずに、IEのバージョンが9未満の場合にのみHTML5 shimをインクルードすることができるため、非常に好都合です。

それに続くセクションには、サイトのロゴを表示するheader、(divタグによる) いくつかの領域、ナビゲーションリンクのリストがあります。

<header class="navbar navbar-fixed-top navbar-inverse">
  <div class="navbar-inner">
    <div class="container">
      <%= link_to "sample app", '#', id: "logo" %>
      <nav>
        <ul class="nav pull-right">
          <li><%= link_to "Home",    '#' %></li>
          <li><%= link_to "Help",    '#' %></li>
          <li><%= link_to "Sign in", '#' %></li>
        </ul>
      </nav>
    </div>
  </div>
</header>

headerタグは、ページのトップに来るべき要素を表します。このheaderタグには、navbarnavbar-fixed-topnavbar-inverseという3つのCSSクラス3がスペース区切りで与えられています。

<header class="navbar navbar-fixed-top navbar-inverse">

すべてのHTML要素には、クラスとidの両方を指定することができます。これらは単なるラベルで、CSSでスタイルを指定するときに便利です (5.1.2)。クラスとIDの主な違いは、クラスはページの中で何度でも使用できるのに対し、IDは一度しか使用することができない点です。今回の場合、すべてのnavbarクラスには、5.1.2でインストールするBootstrapフレームワークによって特別な意味が与えられます。

headerタグの内側には2つのdivタグがあります。

<div class="navbar-inner">
  <div class="container">

divタグは一般的な表示領域を表し、ドキュメントを別々のパーツに分ける以外のことはしません。古いスタイルのHTMLでは、divタグはサイトのほぼすべての領域に使用されますが、HTML5では多くのアプリケーションに共通の領域で使用するheader要素、nav要素、section要素が追加されています。この場合、それぞれのdivにはCSSクラスが与えられています。headerタグのクラスと同様に、これらのクラスもBootstrapにおいて特別な意味を持っています。

divに続いて、埋め込みRubyコードが出現します。

<%= link_to "sample app", '#', id: "logo" %>
<nav>
  <ul class="nav pull-right">
    <li><%= link_to "Home",    '#' %></li>
    <li><%= link_to "Help",    '#' %></li>
    <li><%= link_to "Sign in", '#' %></li>
  </ul>
</nav>

上のコードでは、リンク (3.3.2で、アンカータグaを使用して作成) を生成するために、Railsヘルパーのlink_toを使用しています。link_toの第1引数はリンクテキスト、第2引数はURLです。このURIは5.3.3名前付きルートのURIに変更しますが、今はWebデザインで一般に使用されるスタブURL ’#’にしておきます。第3引数はオプションハッシュで、この場合はサンプルアプリのリンクでCSSのid logoを指定しています (他の3つのリンクにはオプションハッシュが指定されていませんが、必須ではないので構いません)。Railsヘルパーは、このようにオプションのハッシュを取ることがよくあり、これによりRailsのコードから離れることなく任意のHTMLオプションを柔軟に追加することができます。

divの内側の2番目の要素は、リストアイテムタグli順不同リストタグulによって作られた、ナビゲーションリンクのリストです。

<nav>
  <ul class="nav pull-right">
    <li><%= link_to "Home",    '#' %></li>
    <li><%= link_to "Help",    '#' %></li>
    <li><%= link_to "Sign in", '#' %></li>
  </ul>
</nav>

正式にはここでは不要ですが、navタグはその内側がナビゲーションリンクであるという意図を伝える役割があります。ulタグのnavpull-rightクラスは、Bootstrapにおいて特別な意味を持ちます。Railsがこのレイアウトを処理し、埋め込みRubyを評価すると、上のリストは以下に置き換わります。

<nav>
  <ul class="nav pull-right">
    <li><a href="#">Home</a></li>
    <li><a href="#">Help</a></li>
    <li><a href="#">Sign in</a></li>
  </ul>
</nav>

レイアウトの最後の部分は、メインコンテンツ用のdivです。

<div class="container">
  <%= yield %>
</div>

上と同様、containerクラスもBootstrapにおいて特別な意味を持ちます。3.3.4で学んだように、yieldメソッドはWebサイトのレイアウトにページごとの内容を挿入します。

5.1.3で追加するサイトフッターを除いて、これでレイアウトは完成しました。Homeページへアクセスして表示結果を確認することができます。今後登場するスタイル要素を利用できるようにするために、home.html.erbビューに特別な要素をいくつか追加します(リスト5.2)。

リスト5.2 ログインページへのリンクがあるHomeページ。
app/views/static_pages/home.html.erb
<div class="center hero-unit">
  <h1>Welcome to the Sample App</h1>

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

  <%= link_to "Sign up now!", '#', class: "btn btn-large btn-primary" %>
</div>

<%= link_to image_tag("rails.png", alt: "Rails"), 'http://rubyonrails.org/' %>

第7章でサイトにユーザーを追加するときに備えて、最初のlink_toに仮のリンクを作成します。

<a href="#" class="btn btn-large btn-primary">Sign up now!</a>

divタグのCSSクラスhero-unitは、signupボタンのbtnクラス、btn-largeクラス、btn-primaryクラスと同様、Bootstrapにおいて特別な意味を持ちます。

2番目のlink_toでは、引数として画像ファイルのパスと任意のオプションハッシュをとるimage_tagヘルパーの能力が示されています。シンボルを使用して、この場合はalt属性を設定しています。わかりやすくするために、このタグによって生成されるHTMLを以下に示します4

<img alt="Rails" src="/assets/rails.png" />

alt属性は、画像がない場合に代わりに表示される文字列です。また、視覚障害のあるユーザーが使用するスクリーンリーダーでは、この属性が読み上げられてそこに画像があることが示されます。HTML標準では実際に要求されているにも関わらず、画像にalt属性を付けていない手抜きのWebサイトをときどき見かけます。Railsでは幸いにも、この属性を指定せずにimage_tagを呼び出した場合は、画像ファイル名 (拡張子を除く) をデフォルトのalt属性として自動的に付加してくれます。なお、今回は (先頭が大文字の) "Rails"とするためにaltテキストを明示的に設定しています。

注: 以前のバージョンのRailsでは、rails.pngというロゴマークが自動的にすべてのRailsプロジェクトにインストールしていましたが、最新バージョンのRailsではrails newによる生成時にこのロゴマークを含めなくなりました。このロゴマークが必要な方は、Ruby on Rails公式ページ (http://rubyonrails.org/images/rails.png) からダウンロードしてapp/assets/images/ディレクトリにおいてください (mkdirコマンドやGUIベースのファイルマネージャを使用してこのディレクトリを作成する必要があることもあります)。リスト5.2image_tagヘルパーを含めてあるので、RailsはAsset Pipeline (5.2) を使用して、このディレクトリにあるすべての画像を自動的に見つけます。

いよいよ、ここまでの苦労の成果を確認する準備ができました (図5.2)。思っていたよりもみすぼらしいでしょうか。そうかもしれません。しかし、HTML要素に実用的なクラスを与えるという良い仕事ができたのも確かです。さらに、クラスを与えたこの段階で、CSSを使用してサイトにスタイルを与えることができたのは、タイミングとして非常に適切であると思います。

layout_no_logo_or_custom_css_bootstrap_rails_4
図5.2カスタムCSSを使用していないHomeページ (/static_pages/home)。(拡大)

5.1.2BootstrapとカスタムCSS

5.1.1では、多くのHTML要素にCSSクラスを関連付けました。こうしておくことで、CSSベースでレイアウトを構成する際に高い柔軟性を与えてくれます。5.1.1で述べたように、これらのクラスの多くは、Twitterが作成したフレームワークであるBootstrap特有のものです。Bootstrapを使用すると、洗練されたWebデザインとユーザーインターフェイス要素を簡単にHTML5アプリケーションに追加することができます。このセクションでは、サンプルアプリケーションにスタイルを追加するために、カスタムCSSルールとBootstrapを組み合わせます。

最初に、リスト5.3で示しているようにBootstrapを追加しましょう。これは、bootstrap-sass gemを使用してRailsアプリケーションに導入できます (バージョンの不一致によるエラーを直すために、リスト5.3ではSprocketsのバージョンも固定しています。Sprocketsは、5.2で紹介するアセットパイプラインの一部の機能です)。Bootstrapフレームワークでは、動的なスタイルシートを生成するためにLESS CSS言語を使用していますが、RailsのAsset Pipelineはデフォルトでは (LESSと非常によく似た) Sass言語をサポートします (5.2)。そのため、bootstrap-sassは、LESSをSassへ変換し、必要なBootstrapファイルを現在のアプリケーションですべて利用できるようにします5

リスト5.3 Gemfilebootstrap-sassを追加する。
source 'https://rubygems.org'
ruby '2.0.0'
#ruby-gemset=railstutorial_rails_4_0

gem 'rails', '4.0.5'
gem 'bootstrap-sass', '2.3.2.0'
gem 'sprockets', '2.11.0'
.
.
.

いつものようにbundle installを実行して、Bootstrapをインストールします。

$ bundle install

次に、開発中のアプリケーションに変更を反映するために、Webサーバーを再起動します (ほとんどのシステムでは、最初にCtrl-Cを押下してサーバーを停止し、次にrails serverコマンドを実行することでサーバーを再起動できます)。注意: Rails 4.0の場合は、最後にリスト5.4に示すようにconfig/application.rbに1行追加し、bootstrap-sassがAsset Pipelineと互換性を持つようにしておく必要もあります。

リスト5.4 Asset Pipeline互換の行を追加する。
config/application.rb
require File.expand_path('../boot', __FILE__)
.
.
.
module SampleApp
  class Application < Rails::Application
    .
    .
    .
    config.assets.precompile += %w(*.png *.jpg *.jpeg *.gif)
  end
end

アプリケーションにカスタムCSSを追加するための第一段階として、カスタムCSSを格納するための以下のファイルを開きます。

$ subl app/assets/stylesheets/custom.css.scss

このディレクトリ名とファイル名は、どちらも重要です。以下のディレクトリは、

app/assets/stylesheets

Asset Pipeline (5.2)の一部であり、このディレクトリに置かれたスタイルシートはapplication.cssの一部として自動的にWebサイトのレイアウトにインクルードされます。さらに、ファイル名のcustom.css.scssには.cssという拡張子も含まれているので、このファイルはCSSファイルであることが示されています。また、.scssという拡張子も含まれているので、 このファイルはSassを記述できるCSSファイル (Sassy CSS: Scss) であることも示されており、Asset Pipelineはこれを見てSassを処理できるようにします (Sassは5.2.2まで登場しませんが、bootstrap-sass gemが動作するためのおまじないとして必要です)。

カスタムCSS用のファイルを作成したら、リスト5.5のように@importを使用してBootstrapをインクルードします。

リスト5.5 Bootstrap CSSを追加する。
app/assets/stylesheets/custom.css.scss
@import "bootstrap";

図5.3の結果に示したように、このひとつの行にBootstrapフレームワーク全体が含まれます。(場合によっては、Ctrl-Cを使用してローカルのWebサーバーを再起動する必要があるかもしれません。また、スクリーンショットではBootstrap 2.0を使用していますが、このチュートリアルではBootstrap 2.3を使用しているので、外観に多少の違いが生じる可能性があることをご了承ください。これらについて心配する必要はありません。) さて、テキストの配置は今ひとつで、ロゴにはスタイルもありませんが、色使いとsignupボタンはなかなかよい感じになってきました。

sample_app_only_bootstrap_4_0
図5.3Bootstrap CSSとサンプルアプリケーション。(拡大)

次に、リスト5.6に示したように、Webサイト全体にわたってレイアウトと個別のページにスタイルを与えるためのCSSを追加します。リスト5.6には多数の記述ルールがあります。CSSの記述ルールを把握するためには、関心のある箇所をコメントアウトして表示を確認することをお勧めします。CSSでは、/* … */でコメントアウトできるので、調べてみたいコードをこれで囲い、表示がどのように変わるかを確認してみてください。リスト5.6が反映された結果を図5.4に示します。

リスト5.6 すべてのページに適用される共通のスタイルをCSSに追加する。
app/assets/stylesheets/custom.css.scss
@import "bootstrap";

/* universal */

html {
  overflow-y: scroll;
}

body {
  padding-top: 60px;
}

section {
  overflow: auto;
}

textarea {
  resize: vertical;
}

.center {
  text-align: center;
}

.center h1 {
  margin-bottom: 10px;
}
sample_app_universal_4_0
図5.4スペースや共通スタイルを追加した結果。(拡大)

リスト5.6のCSSの形式は一貫しています。CSSルールでは一般に、クラス、id、HTMLタグ、またはそれらの組み合わせ、のいずれかを指定します。そしてその後ろにスタイリングコマンドのリストを記述します。たとえば、以下のコードでは、

body {
  padding-top: 60px;
}

ページ上部に60ピクセルの余白を追加します。headerタグにnavbar-fixed-topクラスが与えられているので、これに従ってBootstrapはナビゲーションバーをページ上部に固定し、ナビゲーションバーの下に余白を置いて主要部分から分離します (デフォルトのnavbarの色は、Bootstrap 2.0から2.1に変わったときに変更されたため、現在の淡色の代わりにダークな色調にしたい場合はnavbar-inverseクラスを使用する必要があります)。また、このルールにある以下のCSSは、

.center {
  text-align: center;
}

centerクラスにtext-align: centerプロパティを関連付けています。言い換えると、.center冒頭のドット.は、このルールがクラスに対してスタイルを適用することを示しています。(冒頭がポンド記号#の場合は、リスト5.8に示したように、そのルールがCSS idに対してスタイルを適用することを示します。この場合、centerクラスに属している (divなどの) タグの内側にある要素は、すべてページ中でセンタリングされることを意味しています (このクラスの例はリスト5.2で参照できます)。

Bootstrapには洗練されたタイポグラフィーを利用できるCSSルールがありますが、ここではさらに、リスト5.7に示したようにサイトのテキストの外観を変えるカスタムCSSルールを追加しましょう。(これらのルールはHomeページですべて適用されるとは限りませんが、サンプルアプリケーションの他の場所でも使用されるものもあります)。リスト5.7を反映した結果を図5.5で確認することができます。

リスト5.7 洗練されたタイポグラフィーを利用するためのCSSを追加する。
app/assets/stylesheets/custom.css.scss
@import "bootstrap";
.
.
.

/* typography */

h1, h2, h3, h4, h5, h6 {
  line-height: 1;
}

h1 {
  font-size: 3em;
  letter-spacing: -2px;
  margin-bottom: 30px;
  text-align: center;
}

h2 {
  font-size: 1.2em;
  letter-spacing: -1px;
  margin-bottom: 30px;
  text-align: center;
  font-weight: normal;
  color: #999;
}

p {
  font-size: 1.1em;
  line-height: 1.7em;
}
sample_app_typography_4_0
図5.5タイポグラフィースタイルを追加する。(拡大)

最後に、いくつかのルールをサイトロゴに追加します。このサイトロゴは「sample app」だけが表示されているシンプルなものです。リスト5.8のCSSは、テキストを大文字に変換し、サイズ、色、配置を変更します (サイトロゴがページで一度しか使用されないことを前提としてCSS idを使用していますが、代わりにクラスを使用することもできます)。

リスト5.8 サイトロゴにCSSを追加する。
app/assets/stylesheets/custom.css.scss
@import "bootstrap";
.
.
.

/* header */

#logo {
  float: left;
  margin-right: 10px;
  font-size: 1.7em;
  color: #fff;
  text-transform: uppercase;
  letter-spacing: -1px;
  padding-top: 9px;
  font-weight: bold;
  line-height: 1;
}

#logo:hover {
  color: #fff;
  text-decoration: none;
}

上のコードのcolor: #fffは、ロゴの色を白に変更します。HTMLの色は、16進数 (基数が16) の3つの数値の組み合わせで表現され、赤、緑、青の三原色に (この順序で) コード化することができます。このコード#ffffffは、3色すべてが最大に使用されており、純白になります。なお、#fffは、完全な#ffffffの短縮形です。CSS標準には、共通HTMLカラーの別名も多数定義されています。たとえば、#fffwhiteと書くこともできます。リスト5.8のCSSの結果は図5.6で確認できます。

sample_app_logo_4_0
図5.6デザインされたロゴとサンプルアプリ。(拡大)

5.1.3パーシャル (partial)

リスト5.1のレイアウトはその目的を果たしていますが、少々散らかっています。HTML shimは、それだけで3行も占有し、風変わりなIE特有の文法を使用しているので、これをうまく隠すことができたらどんなによいでしょう。また、HTMLヘッダーは論理的な単位を形成するため、一箇所にまとめる必要もあります。Railsでは、パーシャル (partial) と呼ばれる機能を使用してこれを実現することができます。最初に、パーシャルを定義するとレイアウトがどのように変わるかを見てみましょう (リスト5.9)。

リスト5.9 Webサイトのレイアウトにスタイルシート用とヘッダー用のパーシャルをそれぞれ追加する。
app/views/layouts/application.html.erb
<!DOCTYPE html>
<html>
  <head>
    <title><%= full_title(yield(:title)) %></title>
    <%= stylesheet_link_tag "application", media: "all",
                                           "data-turbolinks-track" => true %>
    <%= javascript_include_tag "application", "data-turbolinks-track" => true %>
    <%= csrf_meta_tags %>
    <%= render 'layouts/shim' %>
  </head>
  <body>
    <%= render 'layouts/header' %>
    <div class="container">
      <%= yield %>
    </div>
  </body>
</html>

リスト5.9では、以下のようにrenderと呼ばれるRailsヘルパー呼び出しだけを使って、HTML shimのスタイルシート行を置換しています。

<%= render 'layouts/shim' %>

この行では、app/views/layouts/_shim.html.erbというファイルを探してその内容を評価し、結果をビューに挿入しています6。 (<%= ... %>は、テンプレート内でRubyの式を評価するための埋め込みRuby記法であることを思い出してください。評価した結果がテンプレートに挿入されます)。ファイル名_shim.html.erbの前のアンダースコアに注目してください。このアンダースコアは、パーシャルで使用する普遍的な命名規約であり、また、一目見ただけでディレクトリ中のすべてのパーシャルを識別することが可能になります。

もちろん、パーシャルが動作するためには、その中にコンテンツを記述しなければなりません。このshimパーシャルの場合は、リスト5.1のわずか3行のshimコードだけです。追加した結果をリスト5.10に示します。

リスト5.10 HTML shim用のパーシャル。
app/views/layouts/_shim.html.erb
<!--[if lt IE 9]>
<script src="http://html5shim.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->

同様に、他のヘッダーの情報もリスト5.11のパーシャルに移動し、renderを呼び出してレイアウトに挿入することができます。

リスト5.11 サイトヘッダー用のパーシャル。
app/views/layouts/_header.html.erb
<header class="navbar navbar-fixed-top navbar-inverse">
  <div class="navbar-inner">
    <div class="container">
      <%= link_to "sample app", '#', id: "logo" %>
      <nav>
        <ul class="nav pull-right">
          <li><%= link_to "Home",    '#' %></li>
          <li><%= link_to "Help",    '#' %></li>
          <li><%= link_to "Sign in", '#' %></li>
        </ul>
      </nav>
    </div>
  </div>
</header>

これでパーシャルの作成方法がわかりましたので、今度はヘッダーに対応するフッターを同じ方法で追加しましょう。ここまでくれば、ファイル名は_footer.html.erbで、layoutsディレクトリ (リスト5.12) に置けばよいということがわかると思います7

リスト5.12 サイトフッター用のパーシャル。
app/views/layouts/_footer.html.erb
<footer class="footer">
  <small>
    <a href="http://railstutorial.jp/">Rails Tutorial</a>
    by Michael Hartl
  </small>
  <nav>
    <ul>
      <li><%= link_to "About",   '#' %></li>
      <li><%= link_to "Contact", '#' %></li>
      <li><a href="http://news.railstutorial.jp/">News</a></li>
    </ul>
  </nav>
</footer>

ヘッダーの場合と同様に、フッターの中でもlink_toメソッドを使用して、AboutページとContactページへの内部リンクを追加してあります。ひとまず、リンク先のURLは’#’としておきます (headerタグと同様、footerタグもHTML5で新たに追加された要素です)。

フッターパーシャルは、スタイルシートやヘッダーパーシャルのときと同じ方法でレイアウト中に追加できます (リスト5.13)。

リスト5.13 サイトのレイアウトにフッターパーシャルを追加する。
app/views/layouts/application.html.erb
<!DOCTYPE html>
<html>
  <head>
    <title><%= full_title(yield(:title)) %></title>
    <%= stylesheet_link_tag "application", media: "all",
                                           "data-turbolinks-track" => true %>
    <%= javascript_include_tag "application", "data-turbolinks-track" => true %>
    <%= csrf_meta_tags %>
    <%= render 'layouts/shim' %>
  </head>
  <body>
    <%= render 'layouts/header' %>
    <div class="container">
      <%= yield %>
      <%= render 'layouts/footer' %>
    </div>
  </body>
</html>

そのまま実際にフッターを表示してみるとどうにも見苦しいので、リスト5.14でスタイルを若干追加しましょう。スタイルを追加した結果を図5.7に示します。

リスト5.14. サイトのフッター用CSSを追加する。
app/assets/stylesheets/custom.css.scss
.
.
.

/* footer */

footer {
  margin-top: 45px;
  padding-top: 5px;
  border-top: 1px solid #eaeaea;
  color: #999;
}

footer a {
  color: #555;
}

footer a:hover {
  color: #222;
}

footer small {
  float: left;
}

footer ul {
  float: right;
  list-style: none;
}

footer ul li {
  float: left;
  margin-left: 10px;
}
site_with_footer_bootstrap_4_0
図5.7Homeページ (/static_pages/home) にフッターを追加する。(拡大)

5.2SassとAsset Pipeline

最近のRailsに追加された機能の中で最も特筆すべき機能のひとつは、CSS、JavaScript、画像などの静的コンテンツの生産性と管理を大幅に強化する「Asset Pipeline」です。この節では、Asset Pipelineの高度な概要と、Asset Pipelineの一部としてデフォルトで含まれている、Sassと呼ばれる素晴らしいCSS生成ツールの使い方について説明します。

5.2.1Asset Pipeline

Asset Pipelineは、Railsの流儀を守りながら多大な変化をもたらしますが、一般的なRails開発者の視点からは、アセットディレクトリ、マニフェストファイル、プリプロセッサエンジンという、3つの主要な機能が理解の対象となります8。では、それぞれを順に見ていきましょう。

アセットディレクトリ

Rails 3.0以前のバージョンでは、静的ファイルはpublic/以下の次のディレクトリに置かれていました。

  • public/stylesheets
  • public/javascripts
  • public/images

これらのディレクトリ中のファイルは、http://example.com/stylesheetsのようなリクエストによって自動的に配信されます。これは3.0以降も同様です。

Rails 3.1以降では、静的ファイルを目的別に分類する、標準的な3つのディレクトリが使用されるようになりました。Rails 4.0でも同様です。

  • app/assets: 現在のアプリケーション固有のアセット
  • lib/assets: あなたの開発チームによって作成されたライブラリ用のアセット
  • vendor/assets: サードパーティのアセット

これらのディレクトリには、それぞれのアセットクラス用のサブディレクトリがあります。たとえば、app/assetsには次のようなサブディレクトリがあります。

$ ls app/assets/
images      javascripts stylesheets

上記の説明から、5.1.2で取り上げたcustom.css.scssが配置された場所と、その理由について理解することができると思います。custom.css.scssは、サンプルアプリケーション固有のアセットなので、app/assets/stylesheetsに配置されているのです。

マニフェストファイル

アセットを上記の論理的な場所へ配置すれば、マニフェストファイルを使用して、それらをどのように1つのファイルにまとめるのかをRailsに指示することができます。なお、実際にまとめるのはSprockets gemが行います。(マニフェストファイルはCSSとJavaScriptには適用されますが、画像ファイルには適用されません) 。1つの例として、アプリケーションスタイルシート用のマニフェストファイルを見てみましょう (リスト5.15)。

リスト5.15 アプリ固有のCSS用マニフェストファイル。
app/assets/stylesheets/application.css
/*
 * This is a manifest file that'll automatically include all the stylesheets
 * available in this directory and any sub-directories. You're free to add
 * application-wide styles to this file and they'll appear at the top of the
 * compiled file, but it's generally better to create a new file per style
 * scope.
 *= require_self
 *= require_tree .
*/

上の行で重要な部分は実はCSSコメントの中にあります。以下の行は、適切なファイルをインクルードするためにSprocketsによって使用されます。

/*
 .
 .
 .
*= require_self
 *= require_tree . 
*/

以下の行は、

*= require_tree .

app/assets/stylesheetsディレクトリ (サブディレクトリを含む) のすべてのCSSファイルをアプリケーションCSSにインクルードするようにします (このドットも必要です)。以下の行は、

*= require_self

CSSの読み込みシーケンスの中で、application.css自身もインクルードすることを指定しています。

Railsには実用的なデフォルトのマニフェストファイルが付属しているので、Railsチュートリアルでは変更を加える必要がありませんが、もし必要な場合は、Railsガイドの「アセットパイプラインの解説」で詳細な情報を参照できます。

プリプロセッサエンジン

必要なアセットをディレクトリに配置してまとめた後、Railsはさまざまなプリプロセッサエンジンを介してそれらを実行し、ブラウザに配信できるようにそれらをマニフェストファイルを用いて結合し、サイトテンプレート用に準備します。Railsは、どのプリプロセッサを使用するかを、ファイル名の拡張子を使用して判断します。最も一般的な拡張子は、Sass用の.scss、CoffeeScript用の.coffee、埋め込みRuby (ERb) 用の.erbです。3.3.3では最初にERbを、5.2.2ではSassをそれぞれ扱いました。 なお本書では扱いませんが、CoffeeScriptはエレガントで簡潔な言語で、JavaScriptにコンパイルして実行します (興味のある方は、RailsCastの「CoffeeScriptの基礎 (英語)」から始めると良いでしょう)。

プリプロセッサエンジンはつなげて実行する (chain) ことができます。

foobar.js.coffee

上の拡張子の場合、CoffeeScriptプロセッサ経由で実行されます。

foobar.js.erb.coffee

上の拡張子の場合は、CoffeeScriptとERbの両方で実行されます (コードは右から左へと実行されますので、この例ではCoffeeScriptが最初に実行されます)。

本番環境での効率性

Asset Pipelineの最大のメリットの1つは、本番のアプリケーションで効率的になるように最適化されたアセットも自動的に生成されることです。従来は、CSSとJavaScriptを整理するために、機能を個別のファイルに分割し、(インデントを多用して) 読みやすいフォーマットに整えていました。これは、プログラマにとっては便利な方法ですが、本番環境にとっては非効率です。それというのも、最小化されていないCSSやJavaScriptファイルを多数インクルードすると、ページの読み込み時間が著しく遅くなるからです (読み込み時間は、ユーザー体験の質に影響を与える重要な指標の1つです)。一方、Asset Pipelineでは、本番環境に最適化するために、すべてのスタイルシートを1つのCSSファイル (application.css) にまとめ、すべてのJavaScriptファイルを1つのJSファイル (javascripts.js) にまとめてくれます。さらに、それらのファイルすべてに対して (lib/assetsvendor/assetsのファイルも含め) 不要な空白を取り除く処理を行い、ファイルサイズを最小化してくれます。これにより、Asset Pipelineは、2つの異なった状況に対してそれぞれ最高の環境を提供してくれます。つまり、プログラマーに対しては見やすく分割されたフォーマットのファイルを提供し、本番環境に対しては最適化された1つのファイルを提供してくれます。

5.2.2素晴らしい構文を備えたスタイルシート

Sass は、スタイルシートを記述するための言語であり、CSSに比べて多くの点が強化されています。この節では、Sassが提供する2つの重要な機能、ネスト変数について説明します。(3つ目の重要な機能であるミックスインについては、7.1.1で紹介します)。

5.1.2でも簡単に説明しましたが、SassはSCSSというフォーマットに対応しています (.scssという拡張子はSCSSであることを表します)。SCSSは、厳密な意味で、CSS本体を抽象化したフォーマットです。具体的には、SCSS は CSS に新しい機能を追加しただけで、全く新しい構文を定義したようなものではありません9。本書の例では、Bootstrapの恩恵を得るために、私達は最初からSCSSを使用しています。RailsのAsset Pipelineは、.scssという拡張子を持つファイルをSassを使って自動的に処理してくれます。このため、custom.css.scssファイルはSassプリプロセッサによって前処理され、その後ブラウザへの配信に備えてパッケージ化されます。

ネスト

スタイルシート内に共通のパターンがある場合は、要素をネストさせることができます。たとえば、リスト 5.6では、以下のように.center.center h1の両方に対してルールがあります。

.center {
  text-align: center;
}

.center h1 {
  margin-bottom: 10px;
}

上のルールは、Sassを使用して以下のように書き換えることができます。

.center {
  text-align: center;
  h1 {
    margin-bottom: 10px;
  }
}

上の例では、ネストの内側にあるh1というルールは、.centerのルールを継承しています。

今度は、もう少し異なるルールに対してネスト機能を使う例を見てみましょう。リスト 5.8には以下のコードがあります。

#logo {
  float: left;
  margin-right: 10px;
  font-size: 1.7em;
  color: #fff;
  text-transform: uppercase;
  letter-spacing: -1px;
  padding-top: 9px;
  font-weight: bold;
  line-height: 1;
}

#logo:hover {
  color: #fff;
  text-decoration: none;
}

上のコードには#logoというidが2回使用されています。1回目はロゴ自身を定義するために、2回目はhover属性を定義するために使用されています (なおhover属性は、該当する要素の上にマウスポインタをかざしたときの表示を定義します)。2つ目のルールをネストするためには、親属性である#logoを参照する必要があります。このような場合、SCSSでは以下のようにアンパーサンド&を使って実現できます。

#logo {
  float: left;
  margin-right: 10px;
  font-size: 1.7em;
  color: #fff;
  text-transform: uppercase;
  letter-spacing: -1px;
  padding-top: 9px;
  font-weight: bold;
  line-height: 1;
  &:hover {
    color: #fff;
    text-decoration: none;
  }
}

Sassは、SCSSをCSSに変換する際に、&:hover#logo:hoverに置換します。

これらのネスト機能は、フッターのCSSでも使用できます。リスト5.14のコードは、SCSSを使用して以下のように書き換えることができます。

footer {
  margin-top: 45px;
  padding-top: 5px;
  border-top: 1px solid #eaeaea;
  color: #999;
  a {
    color: #555;
    &:hover {
      color: #222;
    }
  }
  small {
    float: left;
  }
  ul {
    float: right;
    list-style: none;
    li {
      float: left;
      margin-left: 10px;
    }
  }
}

リスト 5.14を手作業で変換してみることは、良い演習になります。変換後にもCSSが適切に動作していることを確認してみましょう。

変数

Sassでは、冗長なコードを削除し、より自由な表現を可能にするために、変数が定義できるようになっています。たとえば、リスト 5.7リスト 5.14を見てみると、同じ色を繰り返し参照している箇所があります。

h2 {
  .
  .
  .
  color: #999;
}
.
.
.
footer {
  .
  .
  .
  color: #999;
}

上のコードの#999は薄い灰色を指しています。Sassでは、このような値を変数として定義し、以下のように変数名を与えることができます。

$lightGray: #999;

この機能を使用して、SCSSを以下のように書き直すことができます。

$lightGray: #999;
.
.
.
h2 {
  .
  .
  .
  color: $lightGray;
}
.
.
.
footer {
  .
  .
  .
  color: $lightGray;
}

$lightGrayのような変数名は、#999 のような値よりもわかりやすいので、たとえその変数が繰り返し使われないとしても、変数名を与えることは多くの場合有用です。実際、Bootstrapフレームワークでは、多くの色に対して変数名を定義しています。定義されている変数はBootstrapページの「LESS変数一覧」で参照することができます。このWebサイトでは、SassではなくLESSを使って変数が定義されていますが、bootstrap-sassというgemを使用すれば、Sassでも同様の変数が使えるようになります。LESSとSass の違いを想像するのはそれほど難しくありません。たとえば、LESSではアットマーク@を使用しているのに対して、Sassはドルマーク$を使っていることはすぐにわかります。話を戻して、Bootstrapの変数の一覧表を見ると、薄い灰色に対して以下の変数名が与えられることに気が付きます。

 @grayLight: #999;

これは、bootstrap-sassというgemを使えば、SCSSでも同様に$grayLightという変数が使えることを意味しています。先ほど定義した$lightGrayというカスタム変数に代わりに、用意された変数を使ってみましょう。

h2 {
  .
  .
  .
  color: $grayLight;
}
.
.
.
footer {
  .
  .
  .
  color: $grayLight;
}

今回取り上げたSassのネスト機能や変数機能を使ってSCSSファイルを全面的に書き直すと、リスト 5.16のようになります。このリストでは、Sassの変数 (詳しくはBootstrap LESSの変数一覧を参考にしてください) や、組み込みの色変数 (たとえば#fffにはwhite という変数) を使っています。footerタグのルールが、劇的に向上していることを確認してみてください。

リスト5.16 ネストや変数を使って初期のSCSSファイルを書き直した結果。
app/assets/stylesheets/custom.css.scss
@import "bootstrap";

/* mixins, variables, etc. */

$grayMediumLight: #eaeaea;

/* universal */

html {
  overflow-y: scroll;
}

body {
  padding-top: 60px;
}

section {
  overflow: auto;
}

textarea {
  resize: vertical;
}

.center {
  text-align: center;
  h1 {
    margin-bottom: 10px;
  }
}

/* typography */

h1, h2, h3, h4, h5, h6 {
  line-height: 1;
}

h1 {
  font-size: 3em;
  letter-spacing: -2px;
  margin-bottom: 30px;
  text-align: center;
}

h2 {
  font-size: 1.2em;
  letter-spacing: -1px;
  margin-bottom: 30px;
  text-align: center;
  font-weight: normal;
  color: $grayLight;
}

p {
  font-size: 1.1em;
  line-height: 1.7em;
}


/* header */

#logo {
  float: left;
  margin-right: 10px;
  font-size: 1.7em;
  color: white;
  text-transform: uppercase;
  letter-spacing: -1px;
  padding-top: 9px;
  font-weight: bold;
  line-height: 1;
  &:hover {
    color: white;
    text-decoration: none;
  }
}

/* footer */

footer {
  margin-top: 45px;
  padding-top: 5px;
  border-top: 1px solid $grayMediumLight;
  color: $grayLight;
  a {
    color: $gray;
    &:hover {
      color: $grayDarker;
    }
  }
  small {
    float: left;
  }
  ul {
    float: right;
    list-style: none;
    li {
      float: left;
      margin-left: 10px;
    }
  }
}

Sassを使ってスタイルシートをより簡単にする方法は他にもありますが、今回はその中でも最も重要な機能を使ってリスト 5.16を書き直しました。Sassを使うことによって、素晴らしいスタートを切ることができました。Sassの詳細については、Sass の公式サイト (英語) を参照してください。

5.3レイアウトのリンク

サイトのレイアウトが美しく仕上がりましたので、今度は’#’で代用していたリンクを書き換えてみましょう。もちろん、以下のようにリンクを直接記述することもできます。

<a href="/static_pages/about">About</a>

しかし、上の記法はRails流ではありません。まず、aboutページへのURLは/static_pages/aboutよりも/aboutの方がよいでしょう。さらに、Railsでは以下のようなコードでは名前付きルートを使用するのが慣例となっています。

<%= link_to "About", about_path %>

上のようにすることでコードの意味がわかりやすくなり、about_pathの定義を変えればabout_pathが使用されているすべてのURLを変更できるため、柔軟性が高まります。

今後使用する計画のあるすべてのリンクのリストを、URLとルート (route) のマッピングと共に表5.1に示します。この章の終わりまでに、最後のリンク以外のすべてを実装します。(最後のリンクは第8章で作成します。)

ページURL名前付きルート
Home/root_path
About/aboutabout_path
Help/helphelp_path
Contact/contactcontact_path
Sign up/signupsignup_path
Sign in/signinsignin_path
表 5.1サイトリンクのルート (routing) とURLのマッピング。

先に進む前にContactページを追加しましょう (これは第3章の演習の積み残しです)。Contactページのテストをリスト5.17に示します。これは単にリスト3.19で使用されているテストのパターンに従ったものです。

リスト5.17 Contactページのテスト。
spec/requests/static_pages_spec.rb
require 'spec_helper'

describe "Static pages" do
  .
  .
  .
  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("Ruby on Rails Tutorial Sample App | Contact")
    end
  end
end

以下のテストは失敗するはずです。

$ bundle exec rspec spec/requests/static_pages_spec.rb

アプリケーションコードは、3.2.2のAboutページへの追加と良く似ています。最初にルート (リスト5.18) を更新します。次にcontactアクションをStaticPagesコントローラ (リスト5.19) に追加します。最後にContactビュー (リスト5.20) を作成します。

リスト5.18 Contactページ用のルートを追加する。
config/routes.rb
SampleApp::Application.routes.draw do
  get "static_pages/home"
  get "static_pages/help"
  get "static_pages/about"
  get "static_pages/contact"
  .
  .
  .
end
リスト5.19 Contactページ用のアクションを追加する。
app/controllers/static_pages_controller.rb
class StaticPagesController < ApplicationController
  .
  .
  .
  def contact
  end
end
リスト5.20 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>

今度はテストが成功することを確認してください。

$ bundle exec rspec spec/requests/static_pages_spec.rb

5.3.1 ルートのテスト

静的ページの結合テストを書いておいたので、ルートに対するテストはシンプルです。コードに直接書かれているアドレスを表5.1にある名前付きルートにそれぞれ置き換えるだけです。つまり、以下のコードの場合、

visit '/static_pages/about'

上を以下のように変更します。

visit about_path

他のページについても同様に変更します。変更の結果をリスト5.21に示します。

リスト5.21 名前付きルートのテスト。
spec/requests/static_pages_spec.rb
require 'spec_helper'

describe "Static pages" do

  describe "Home page" do

    it "should have the h1 'Sample App'" do
      visit root_path
      expect(page).to have_content('Sample App')
    end

    it "should have the base title" do
      visit root_path
      expect(page).to have_title("Ruby on Rails Tutorial Sample App")
    end

    it "should not have a custom page title" do
      visit root_path
      expect(page).not_to have_title('| Home')
    end
  end

  describe "Help page" do

    it "should have the h1 'Help'" do
      visit help_path
      expect(page).to have_content('Help')
    end

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

  describe "About page" do

    it "should have the h1 'About Us'" do
      visit about_path
      expect(page).to have_content('About Us')
    end

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

  describe "Contact page" do

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

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

いつもと同様に、今度のテストは赤色 (失敗) になるはずです。

$ bundle exec rspec spec/requests/static_pages_spec.rb

ところで、もしリスト5.21が繰り返しが多く冗長だと感じたなら、それはあなただけではありません。この乱雑な状態を5.3.4で美しい宝石へとリファクタリングする予定です。

5.3.2 Railsのルート

URLに対するテストができあがったので、それらを実際に利用できるようにしましょう。3.1.2で説明したとおり、RailsがURLマッピングに使用するファイルはconfig/routes.rbです。デフォルトのルートファイルの内容を見てみると、かなり乱雑になっています。しかし、それらはすべてコメントアウトされたルートマッピングの例であり、必要な乱雑さです。時間のあるときにこのルートマッピングを読んでみることをお勧めします。また、より詳細なルーティングの扱いについては、Railsガイドの「Railsのルーティング」を参照することをお勧めします。

名前付きルートを定義するため、以下のようなルールを置き換えます。

get 'static_pages/help'

上のルーティングを下記のように置き換えます。

match '/help', to: 'static_pages#help', via: 'get'

このルーティングは、/help (GETリクエストに応答する) で有効なページと、そのページへのパスを返すhelp_pathという名前の名前付きルートの両方を準備します (実際には、matchの箇所にgetを使用しても同じ名前付きルートになりますが、matchを利用する方がよりRailsの慣例に従っています)。

このパターンを他の静的ページに適用すると、リスト5.22になります。唯一の例外は、リスト5.24で取り扱うHomeページです。

リスト5.22 静的ページのルート。
config/routes.rb
SampleApp::Application.routes.draw do
  match '/help',    to: 'static_pages#help',    via: 'get'
  match '/about',   to: 'static_pages#about',   via: 'get'
  match '/contact', to: 'static_pages#contact', via: 'get'
  .
  .
  .
end

リスト5.22コードを注意深く読むことで、このコードの動作を理解できるでしょう。たとえば以下のコードの場合について説明します。

match '/about', to: 'static_pages#about',  via: 'get'

上のコードは、’/about’へのGETリクエストにマッチします。次にStaticPagesコントローラのaboutアクションにルーティングされます。変更前の以下のコードは、より明示的でした。

get 'static_pages/about'

このコードも同じページへたどり着きますが、/aboutの方が簡潔です。さらに前述したように、match ’/about’というコードは自動的にコントローラとビューで使用する名前付きルートを生成します。

about_path -> '/about'
about_url  -> 'http://localhost:3000/about'

about_urlフルURL http://localhost:3000/about であることに注目してください (なお、localhost:3000は本番環境でexample.comのようなドメイン名へ置き換えられます)。5.3で説明したように、/about のみを取得する場合は about_pathを使います。なお、Railsチュートリアルでは、path書式を使用する一般的な規約に従い、リダイレクトの場合のみurl書式を使用します。これは、HTTP標準では技術的にリダイレクト後にフルURLが要求されるためです。ただし、ほとんどのブラウザではどちらの方法でも動作します。

ルーティングが定義されたので、Help、About、Contactページのテストはパスするはずです。

$ bundle exec rspec spec/requests/static_pages_spec.rb

これで、失敗するテストはHomeページを残すだけとなりました。

Homeページへのルートマッピングを作成する際に、以下のようなコードを使用することも一応可能です

match '/', to: 'static_pages#home', via: 'get'

しかし、実際にはルート (root) については上のように書く必要はありません。Railsでは、routesファイル (リスト5.23) の下の方のコメント内に、ルート(root) URL / (“スラッシュ”) 用の特別な記法が書かれています。

リスト5.23 ルート (root) へのルーティングを定義する、コメント内のヒント。
config/routes.rb
SampleApp::Application.routes.draw do
  .
  .
  .
  # You can have the root of your site routed with "root"
  # root 'welcome#index'
  .
  .
  .
end

リスト5.23のコメントを参考に、リスト5.24のようにルート (root) URL / をHomeページへルーティングします。

リスト5.24 ルート (root) へのルーティングのためのマッピングを追加する。
config/routes.rb
SampleApp::Application.routes.draw do
  root  'static_pages#home'
  match '/help',    to: 'static_pages#help',    via: 'get'
  match '/about',   to: 'static_pages#about',   via: 'get'
  match '/contact', to: 'static_pages#contact', via: 'get'
  .
  .
  .
end

上のコードは、ルート (root) URLである / を /static_pages/home に割り当てています。これにより、http://localhost:3000/の表示が図1.3のデフォルトのRailsページから変更されます。また、URLヘルパーに以下の設定を与えます。

root_path => '/'
root_url  => 'http://localhost:3000/'

これで静的ページへのルートがすべて動作し、テストもすべてパスするはずです。

$ bundle exec rspec spec/requests/static_pages_spec.rb

後は、レイアウトのリンクをこれらの名前付きルートで埋めればよいのです。

5.3.3名前付きルート

レイアウトのリンクを有効にするために、5.3.2で作成した名前付きルートを記入しましょう。そのためには、link_toメソッドの2番目の引数に適切な名前付きルートを指定する必要があります。たとえば以下のコードの場合、

<%= link_to "About", '#' %>

上を以下のように変更します。

<%= link_to "About", about_path %>

最初に、HomeページとHelpページへのリンクを持つヘッダーパーシャル_header.html.erb (リスト5.25) から取りかかります。ヘッダーパーシャルでは、Web共通の慣習に従って、ロゴにもHomeページへのリンクを追加します。

リスト5.25 ヘッダーパーシャルにリンクを追加する。
app/views/layouts/_header.html.erb
<header class="navbar navbar-fixed-top navbar-inverse">
  <div class="navbar-inner">
    <div class="container">
      <%= link_to "sample app", root_path, id: "logo" %>
      <nav>
        <ul class="nav pull-right">
          <li><%= link_to "Home",    root_path %></li>
          <li><%= link_to "Help",    help_path %></li>
          <li><%= link_to "Sign in", '#' %></li>
        </ul>
      </nav>
    </div>
  </div>
</header>

[Sign in] リンクの名前付きルートは第8章で作成するため、今の段階では’#’のままにしておきます。

フッターパーシャル_footer.html.erbにもリンクがあります。これらはAboutページとContactページへのリンクです (リスト5.26)。

リスト5.26 フッターパーシャルにリンクを追加する。
app/views/layouts/_footer.html.erb
<footer class="footer">
  <small>
    <a href="http://railstutorial.org/">Rails Tutorial</a>
    by Michael Hartl
  </small>
  <nav>
    <ul>
      <li><%= link_to "About",   about_path %></li>
      <li><%= link_to "Contact", contact_path %></li>
      <li><a href="http://news.railstutorial.jp/">News</a></li>
    </ul>
  </nav>
</footer>

これで、レイアウトに第3章で作成したすべての静的ページへのリンクができました。たとえば/aboutの場合はAboutページ (図5.8) に移動します。

ところで、実際にレイアウト上にリンクが存在するかどうかをまだテストしていませんが、テスト方法のヒントとして、名前付きルートが定義されていなければテストは失敗するということに注目してください。このことは、リスト5.22のルーティング定義をコメントアウトし、テストスイートを実行することで確認できます。リンクが実際に正しい場所を指していることを確認するテストの手法については、5.6で説明します。

about_page_styled
図5.8/aboutで表示されるAboutページ。(拡大)

5.3.4RSpecを洗練させる

5.3.1で、静的ページのテストが冗長で繰り返しが多くなってきたことを指摘しました (リスト5.21)。この節では、RSpecの最新の機能を使い、テストをより簡潔で洗練されたものにします。

テストの改善方法について、いくつかの例を見てみましょう。

describe "Home page" do

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

  it "should have the base title" do
    visit root_path
    expect(page).to have_title("Ruby on Rails Tutorial Sample App")
  end

  it "should not have a custom page title" do
    visit root_path
    expect(page).not_to have_title('| Home')
  end
end

まず、上の3つの例はいずれもルートへのアクセスを含んでいることに気付きます。beforeブロックを使用することでこの冗長箇所を除くことができます。

describe "Home page" do
  before { visit root_path }

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

  it "should have the base title" do
    expect(page).to have_title("Ruby on Rails Tutorial Sample App")
  end

  it "should not have a custom page title" do
    expect(page).not_to have_title('| Home')
  end
end

上のコードでは以下を使用しました。

before { visit root_path }

これにより、それぞれの例の前にルートパスへのアクセスを実行します (beforeメソッドは、別名でもあるbefore(:each)で呼ぶこともできます)。

冗長性の原因は他にもあります。

it "should have the content 'Sample App'" do

上と以下はどちらも本質的には同じ内容です。

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

さらに、どちらの例もpage変数を参照しています。以下のように、pageはテストの主題 (subject) であることをRSpecに伝えることにより、冗長の原因を排除できます。

subject { page }

次に、以下のようにitメソッドの変種を使用することにより、コードと記述を1行に収めます。

it { should have_content('Sample App') }

subject { page }と記述したことにより、shouldの呼び出しは自動的に、Capybara (3.2.1)により提供されるpage変数を使用します。

これらの変更を加えることでHomeページのテストはより簡潔になります。

  subject { page }

  describe "Home page" do
    before { visit root_path }

    it { should have_content('Sample App') }
    it { should have_title("Ruby on Rails Tutorial Sample App") }
    it { should_not have_title('| Home') }
  end

上のコードは以前より良くなりましたが、まだタイトルのテストが少し長すぎます。確かに、リスト5.21にあるほとんどのタイトルのテストで以下の長いテキストが使用されています。

"Ruby on Rails Tutorial Sample App | About"

3.5の演習では、base_title変数を定義し、式展開を使用することでこの冗長性を排除することを提案しました (リスト3.31)。リスト4.2full_titleヘルパーと同様のfull_titleを定義することで、さらに改良することができます。これを行うには、RSpecユーティリティで使用するspec/supportディレクトリとutilities.rbファイルを作成します (リスト5.27)。

リスト5.27 full_titleメソッドを持つRSpecユーティリティ用ファイル。
spec/support/utilities.rb
def full_title(page_title)
  base_title = "Ruby on Rails Tutorial Sample App"
  if page_title.empty?
    base_title
  else
    "#{base_title} | #{page_title}"
  end
end

もちろんこれは、基本的にリスト4.2のヘルパーの複製です。しかし、2つの独立したメソッドがあることで基本タイトルのタイプミスを検出することができます。ヘルパーのコードとテストコードがまったく同じになっているというのは、やや疑問の残る設計であり、演習 (5.6) ではもっと良い (少しだけ高度な) アプローチとして、オリジナルのfull_titleヘルパーを直接テストします。

spec/supportディレクトリはRSpecによって自動的に読み込まれるため、Homeテストは以下のように書くことができます。

  subject { page }

  describe "Home page" do
    before { visit root_path }

    it { should have_content('Sample App') }
    it { should have_title(full_title('')) }
    it { should_not have_title('| Home') }
  end

Homeページで使用したのと同じ方法で、Help、About、Contactページのテストも単純化することができます。変更の結果をリスト5.28に示します。

リスト5.28 整えられた静的ページ用テスト。
spec/requests/static_pages_spec.rb
require 'spec_helper'

describe "Static pages" do

  subject { page }

  describe "Home page" do
    before { visit root_path }

    it { should have_content('Sample App') }
    it { should have_title(full_title('')) }
    it { should_not have_title('| Home') }
  end

  describe "Help page" do
    before { visit help_path }

    it { should have_content('Help') }
    it { should have_title(full_title('Help')) }
  end

  describe "About page" do
    before { visit about_path }

    it { should have_content('About') }
    it { should have_title(full_title('About Us')) }
  end

  describe "Contact page" do
    before { visit contact_path }

    it { should have_content('Contact') }
    it { should have_title(full_title('Contact')) }
  end
end

テストは今度もパスするはずです。

$ bundle exec rspec spec/requests/static_pages_spec.rb

リスト5.28のRSpecスタイルは、リスト5.21のスタイルよりもずっと簡潔でわかりやすくなっています。実は、さらに簡潔でわかりやすくすることが可能です (5.6)。サンプルアプリケーションの今後の開発では、そのさらに簡潔なスタイルを可能な限り使用することにします。

5.4ユーザー登録: 最初のステップ

この節では、レイアウトとルーティングの取り組みにおける頂点として、ユーザー登録ページへのルーティングを作成します。そのために2番目のコントローラを作成することになります。これは、Webサイトでユーザー登録を行えるようにするための最初の重要な一歩となります。次の一歩であるユーザーのモデリングは第6章で行い、第7章でユーザー登録が完成します。

5.4.1Usersコントローラ

3.1で最初のコントローラであるStaticPagesコントローラを作成してからしばらく経ちました。今度は2番目のコントローラであるUsersコントローラを作成しましょう。1番目のときと同様、generateを実行して、現時点での要求である新規ユーザー用のユーザー登録ページ (スタブ) を持つ、最も簡単なコントローラを作成します。Railsで好まれているRESTアーキテクチャの規約に従い、新規ユーザー用のアクションをnewにすることに決め、generate controllerの引数として渡して自動的にアクションを作成します (リスト5.29)。

リスト5.29 Usersコントローラの生成 (newアクションを追加)。
$ rails generate controller Users new --no-test-framework
      create  app/controllers/users_controller.rb
       route  get "users/new"
      invoke  erb
      create    app/views/users
      create    app/views/users/new.html.erb
      invoke  helper
      create    app/helpers/users_helper.rb
      invoke  assets
      invoke    coffee
      create      app/assets/javascripts/users.js.coffee
      invoke    scss
      create      app/assets/stylesheets/users.css.scss

上のコマンドにより、newアクションを持つUsersコントローラ(リスト5.30)と、スタブのユーザービューを作成します(リスト5.31)。

リスト5.30 newアクションを持つ最初のUsersコントローラ。
app/controllers/users_controller.rb
class UsersController < ApplicationController
  def new
  end

end
5.31 Users用の最初のnewアクション。
app/views/users/new.html.erb
<h1>Users#new</h1>
<p>Find me in app/views/users/new.html.erb</p>

5.4.2 ユーザー登録用URL

5.4.1のコードにより、新規ユーザー用の動作するページが/users/new にできました。ここで表5.1を思い出していただきたいのですが、URLは/users/newではなく表のとおりに/signupにしたいと思います。5.3のときと同様、最初にいくつかの結合テストを作成しましょう。以下を実行してテスト用ファイルを生成します。

$ rails generate integration_test user_pages

次に、リスト5.28の静的ページspecのひな形に従い、 リスト5.32に示すように、ユーザーページのh1titleタグの内容をテストするコードを記入します。

リスト5.32 ユーザー登録ページへのテストを含む、ユーザー用の最初のspec。
spec/requests/user_pages_spec.rb
require 'spec_helper'

describe "User pages" do

  subject { page }

  describe "signup page" do
    before { visit signup_path }

    it { should have_content('Sign up') }
    it { should have_title(full_title('Sign up')) }
  end
end

いつもと同様、これらのテストを以下のようにrspecコマンドで実行できます。

$ bundle exec rspec spec/requests/user_pages_spec.rb

上のように特定のファイルを1つ渡す代わりに、以下のようにrequestsディレクトリ全体を渡すと、すべてのリクエストspecのテストを実行できることを覚えておくと良いでしょう。

$ bundle exec rspec spec/requests/

上のパターンから、それ以外のディレクトリを含むすべてのspecを実行する以下の方法も容易に想像がつくと思います。

$ bundle exec rspec spec/

完全を期して、今後はチュートリアルの最後まで基本的に上の方法を使用してテストをフル実行します。なお、Rakeタスクでspecのテストスイートを実行できることも覚えておくとよいでしょう (他の開発者が使っているのを見たことがあるかもしれません)。

$ bundle exec rake spec

(実際にはrakeとタイプするだけで済みます。rakeのデフォルトの動作はテストスイートの実行です)。注意: 現在のサンプルアプリケーションに対して、rakeを使用してこのテストを実行するとエラーが発生します。これはテスト環境用データベースが正しく設定されていないためです。これについては6.2.1で対応します。

Usersコントローラは、作成時に既にnewアクションを持っているため、後はテストをパスさせるために正しいルートとビューの中身を作成すればよいのです。リスト5.22の例に従い、ユーザー登録URL用にmatch ’/signup’のルールを追加します。(リスト5.33)。

リスト5.33 ユーザー登録ページへのルート。
config/routes.rb
SampleApp::Application.routes.draw do
  get "users/new"

  root  'static_pages#home'
  match '/signup',  to: 'users#new',            via: 'get'
  match '/help',    to: 'static_pages#help',    via: 'get'
  match '/about',   to: 'static_pages#about',   via: 'get'
  match '/contact', to: 'static_pages#contact', via: 'get'
  .
  .
  .
end

リスト5.29では、Usersコントローラを生成したときに自動的に生成されたget "users/new"のルールはそのままにしてあることに注目してください。現時点では、このルールは’users/new’のルーティングが動作するために必要ですが、適切なREST規約 (表2.2) に従っていません。これは7.1.2で取り除きます。

テストをパスさせるために今必要なのは、“Sign up” のタイトルとヘッダーがあるビューです (リスト5.34)。

リスト5.34 最初のユーザー登録ページ (スタブ)。
app/views/users/new.html.erb
<% provide(:title, 'Sign up') %>
<h1>Sign up</h1>
<p>Find me in app/views/users/new.html.erb</p>

この時点で、リスト5.32のテストはパスするはずです。残っている作業は、Homeページのボタンに適切なリンクを追加することです。他のルートと同様、match ’/signup’と記述したことでsignup_pathという名前付きルートができ、それをリスト5.35で使用します。

リスト5.35 ボタンをユーザー登録ページにリンクする。
app/views/static_pages/home.html.erb
<div class="center hero-unit">
  <h1>Welcome to the Sample App</h1>

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

  <%= link_to "Sign up now!", signup_path, class: "btn btn-large btn-primary" %>
</div>

<%= link_to image_tag("rails.png", alt: "Rails"), 'http://rubyonrails.org/' %>

これで、少なくともサインインのルートを追加するまでの間、リンクと名前付きルートが完成しました(第8章)。結果を図5.9の新規ユーザーのページ (URI /signup) に示します。

new_signup_page_bootstrap
図5.9/signupで表示される新しいユーザー登録ページ 。(拡大)

この時点で、テストはパスするはずです。

$ bundle exec rspec spec/

5.5最後に

この章では、アプリケーションのレイアウトを形にし、ルーティングを洗練させました。本書では、以後サンプルアプリケーションを肉付けすることに専念します。最初に、ユーザー登録、サインイン、サインアウトできるユーザーを追加します。次に、マイクロポストを追加します。最後に、他のユーザーをフォローできるようにします。

Gitを使っている方は、この時点でmasterブランチに変更をマージしてください。

$ git add .
$ git commit -m "Finish layout and routes"
$ git checkout master
$ git merge filling-in-layout

続いてGitHubにプッシュしても構いません。

$ git push

最後にHerokuへデプロイすることもできます。

$ git push heroku

これで、本番環境のサーバーでサンプルアプリケーションが動作しているはずです。

$ heroku open

不具合が発生した場合は、以下のコマンドを試してみてください。

$ heroku logs

上のコマンドを使用してHerokuのログファイルを参照し、エラーをデバッグしてください。

5.6演習

  1. リスト5.28の静的ページのテストコードは簡潔ですが、まだ繰り返しが残っています。RSpecには、冗長性を排除するためのShared Examplesと呼ばれる機能があります。リスト5.36の例に従い、Help、AboutとContactページで不足しているテストを補ってください。リスト3.31で簡単に紹介したletコマンドは、要求 (変数が使用されるときなど) に応じて与えられた値を持つ「ローカル変数」を作成することに注意してください。対照的にインスタンス変数は、要素代入 (assignment) されたときに作成されます。
  2. 既にお気付きの方もいると思いますが、これまで行なってきた、レイアウト上のリンクのルーティングテストは、そのリンクが実際に正しいページへのリンクになっているかどうかをチェックしていません。このようなテストを実装する方法のひとつは、RSpecの結合テスト内でvisitclick_linkを使用することです。レイアウトのリンクが正しく定義されていることを確認するコードを追加して、リスト5.37を補ってください。
  3. リスト5.38に示すように、元のヘルパーメソッドに対するテストを書き、それによってリスト5.27full_titleテストヘルパーへの依存を解消してください。(spec/helpersディレクトリとapplication_helper_spec.rbファイルの両方を作成する必要があるでしょう)。次に、リスト5.39のコードを使用して、それをテストへincludeしてください。テストスイートを実行して、新しいコードに問題がないことを確認してください。注意: リスト5.38では、6.2.4で学ぶ正規表現を使用しています (この演習を提案し、コードを提供してくれたAlex Chaffeeに感謝します)。
リスト5.36 RSpecのShared Exampleを使用してテストの冗長性を排除する。
spec/requests/static_pages_spec.rb
require 'spec_helper'

describe "Static pages" do

  subject { page }

  shared_examples_for "all static pages" do
    it { should have_content(heading) }
    it { should have_title(full_title(page_title)) }
  end

  describe "Home page" do
    before { visit root_path }
    let(:heading)    { 'Sample App' }
    let(:page_title) { '' }

    it_should_behave_like "all static pages"
    it { should_not have_title('| Home') }
  end

  describe "Help page" do
    .
    .
    .
  end

  describe "About page" do
    .
    .
    .
  end

  describe "Contact page" do
    .
    .
    .
  end
end
リスト5.37 レイアウトのリンクをテストする。
spec/requests/static_pages_spec.rb
require 'spec_helper'

describe "Static pages" do
  .
  .
  .
  it "should have the right links on the layout" do
    visit root_path
    click_link "About"
    expect(page).to have_title(full_title('About Us'))
    click_link "Help"
    expect(page).to # ここにコードを記入
    click_link "Contact"
    expect(page).to # ここにコードを記入
    click_link "Home"
    click_link "Sign up now!"
    expect(page).to # ここにコードを記入
    click_link "sample app"
    expect(page).to # ここにコードを記入
  end
end
リスト5.38 full_titleヘルパーに対するテスト。
spec/helpers/application_helper_spec.rb
require 'spec_helper'

describe ApplicationHelper do

  describe "full_title" do
    it "should include the page title" do
      expect(full_title("foo")).to match(/foo/)
    end

    it "should include the base title" do
      expect(full_title("foo")).to match(/^Ruby on Rails Tutorial Sample App/)
    end

    it "should not include a bar for the home page" do
      expect(full_title("")).not_to match(/\|/)
    end
  end
end
リスト5.39 full_titleテストヘルパーを単純に includeで置換する。
spec/support/utilities.rb
include ApplicationHelper
  1. サンプルアプリケーションでBootstrapを使用するための変換作業で、目覚ましい活躍を見せて助けてくれた読者のColm Tuiteに感謝します。
  2. Ruby on Railsチュートリアルのモックアップは、Mockingbirdというオンラインモックアップアプリケーションで作成しています。
  3. CSSクラスは、Rubyのクラスとはまったく関係がありません。
  4. 既にお気付きだと思いますが、imgタグは<img>...</img>ではなく<img ... />と書きます。この書式に従うタグは 閉じタグとして知られています。 
  5. Asset PipelineでLESSを使うこともできます。詳細はless-rails-bootstrap gemを参照してください。
  6. 多くのRails開発者は、異なるビューの間で共通に使用するパーシャルを保存するディレクトリとして、sharedディレクトリを使用します。著者は、複数のビューで共有するユーティリティパーシャルについてはsharedフォルダに保存し、文字どおり全ページ (サイトレイアウトの一部として) 共通のパーシャルについてはlayoutsディレクトリへ保存することを好んでいます (sharedディレクトリは第7章で作成します)。著者はこのように分割保存するのが論理的であると考えますが、sharedフォルダにすべて保存しても問題なく動作します。
  7. footerタグと.footerクラスを両方使用していることについて疑問に思う方がいるかもしれません。その理由は、footerタグとする方が読み手にとって意味が明確であるのと、.footerクラスはBootstrapで使用するためです。footerdivに置き換えても動作は変わりません。
  8. このチュートリアル構成は、Michael Erasmusによる素晴らしいブログ記事「5分でわかるRails 3のAsset Pipeline (英語)」をもとにしています。詳細については、Railsガイドの「アセットパイプライン」の項を参照してください。
  9. Sassでもサポートされている古い.sassというフォーマットでは、冗長性の少ない (括弧の少ない) 新しい言語を定義しますが、既存のプロジェクトには若干不便であり、既にCSSに慣れ親しんだ人にとっては学習が面倒でもあります。

第6章ユーザーのモデルを作成する

第5章では、新しいユーザーを作成するためのスタブページを作ったところで終わりました (5.4)。これから4つの章を通して、ユーザー登録ページを作っていくことにしましょう。最初の一番重要なステップは、サイトのユーザー用のデータモデルの作成と、データを保存する手段の確保です。第7章では、ユーザーがサイトにユーザー登録できるようにし、ユーザープロファイルのためのページを作成します。ユーザー登録ができるようになると、次にサインイン、サインアウトもできるようにします (第8章)。そして第9章 (9.2.1) では、不正なアクセスから守る方法を学びます。まとめると、第6章から第9章を通して、Railsのログインと認証のシステムをひととおり開発します。ご存知の方もいると思いますが、Railsでは既にさまざまな認証方法が利用可能です。コラム 6.1では、最初に少なくとも一度は自分で認証システムを作ってみることをお勧めする理由について説明しています。

この章は長いうえに、学ぶことがたくさんあります。特に、これまでデータモデリングをしたことがない人にとっては、もしかすると、これまでとは違った難しさを感じるかもしれません。しかし、この章が終わるまでには、ユーザー情報の検証、保存、取得ができる極めて強力なシステムを作成します。

Gitでバージョン管理を行なっているのであれば、このタイミングでユーザーをモデリングするためのトピックブランチを作成しておいてください。

$ git checkout master
$ git checkout -b modeling-users

(最初の行はmasterブランチから作業を始めることを確認するためのものです。そして、modeling-usersトピックブランチはmasterブランチを基に作成します。もしすでにmasterブランチにいる場合は、このコマンドを実行する必要はありません)。

6.1 Userモデル

ここから3つの章にわたる最終目標はユーザー登録ページ (図6.1のモックアップ) を作成することですが、今のままでは新しいユーザーの情報を受け取っても保存する場所がないので、いきなりページを作成するわけにはいきません。ユーザー登録でまず初めにやることは、それらの情報を保存するためのデータ構造を作成することです。

signup_mockup_bootstrap
図6.1ユーザー登録ページのモックアップ。(拡大)

Railsでは、データモデルで使用するデフォルトのデータ構造のことをモデルと呼びます (1.2.6で言うMVCのMのことです)。Railsでは、データを永続化するデフォルトの解決策として、データベースを使用してデータを長期間保存します。また、データベースとやりとりするデフォルトのRailsライブラリはActive Recordと呼ばれます1。Active Recordは、データオブジェクトの作成/保存/検索のためのメソッドを持っています。これらのメソッドを使用するのに、リレーショナルデータベースで使うSQL (Structured Query Language)2を意識する必要はありません。さらに、Railsにはマイグレーションという機能があります。データの定義をRubyで記述することができ、SQLのDDL (Data Definition Language)を新たに学ぶ必要がありません。Railsは、データストアの詳細からほぼ完全に私たちを切り離してくれます。本書では、SQLiteを開発環境で使い、またPostgreSQLを (Herokuでの) 開発環境で使います (1.4)。Railsは、本番アプリケーションですら、データの保存方法の詳細についてほとんど考える必要がないくらいよくできています。

6.1.1データベースの移行

4.4.5で扱ったカスタムビルドクラスのUserを思い出してください。このクラスは、nameemailを属性に持つユーザーオブジェクトでした。このクラスは役に立つ例として提供されましたが、Railsにとって極めて重要な部分である永続性という要素が欠けていました。RailsコンソールでUserクラスのオブジェクトを作っても、コンソールからexitするとそのオブジェクトはすぐに消えてしまいました。この節での目的は、簡単に消えることのないユーザーのモデルを構築することです。

4.4.5のユーザークラスと同様に、nameemailの2つの属性からなるユーザーをモデリングするところから始めましょう。後者のemailを一意のユーザー名として使用します3 (パスワードのための属性は6.3で扱います) 。リスト4.9では、以下のようにRubyのattr_accessorメソッドを使用しました。

class User
  attr_accessor :name, :email
  .
  .
  .
end

それとは対照的に、Railsでユーザーをモデリングするときは、属性を明示的に識別する必要がありません。上で簡潔に述べたように、Railsはデータを保存する際にデフォルトでリレーショナルデータベースを使用します。リレーショナルデータベースは、データで構成されるテーブルからなり、各行はデータ属性のカラム (列) を持ちます。たとえば、nameとemailを持つユーザーを保存するのであれば、nameemailのカラムを持つusersテーブルを作成します (各行はひとりのユーザーを表します)。カラムをこのように名付けることによって、Active RecordでUserオブジェクトの属性を利用できるようになります。

それでは実際どのように動作するのか見てみましょう (ここまでの説明が抽象的でわかりにくいかもしれませんが、少しだけご辛抱願います。6.1.3から使用しているコンソールの例と、図6.3図6.6にあるデータベースブラウザのスクリーンショットが理解を助けてくれるでしょう)。リスト5.29で、ユーザーコントローラ (とnewアクション) を作ったときに使った以下のコマンドを思い出してみてください。

$ rails generate controller Users new --no-test-framework

上のコマンドはコントローラを作成しましたが、同様にモデルを作成するコマンドとして、generate modelがあります。nameemailの2つの属性を持つUserモデルを作成するコマンドをリスト6.1に示します。

リスト6.1 Userモデルの作成。
$ rails generate model User name:string email:string
      invoke  active_record
      create    db/migrate/[timestamp]_create_users.rb
      create    app/models/user.rb
      invoke    rspec
      create      spec/models/user_spec.rb

(コントローラ名には複数形を使い、モデル名には単数形を用いるという慣習を頭に入れておいてください。コントローラはUsersでモデルはUserです)。name:stringemail:stringオプションのパラメータを渡すことによって、データベースで使用したい2つの属性をRailsに伝えます。このときに、これらの属性の型情報も一緒に渡します (この場合はstring)。リスト3.4リスト5.29でアクション名を使用して生成した例と比較してみてください。

リスト6.1にあるgenerateコマンドの結果のひとつとして、マイグレーションと呼ばれる新しいファイルが生成されます。マイグレーションは、データベースの構造をインクリメンタルに変更する手段を提供します。それにより、要求が変更された場合にデータモデルを適合させることができます。このUserモデルの例の場合、マイグレーションはモデル生成スクリプトによって自動的に作られました。リスト6.2に示したようにnameemailの2つのカラムを持つusersテーブルを作成します (6.2.56.3で、マイグレーションを一から手動で作成する方法について説明します)。

リスト6.2 (usersテーブルを作るための) Userモデルのマイグレーション。
db/migrate/[timestamp]_create_users.rb
class CreateUsers < ActiveRecord::Migration
  def change
    create_table :users do |t|
      t.string :name
      t.string :email

      t.timestamps
    end
  end
end

マイグレーションファイル名の先頭には、それが生成された時間のタイムスタンプが追加されます。以前はインクリメンタルな整数が追加されましたが、複数の開発者によるチームでは、複数のプログラマが同じ整数を持つマイグレーションを生成してしまい、コンフリクトを引き起こしていました。現在のタイムスタンプによる方法であれば、まったく同時にマイグレーションが生成されるという通常ではありえないことが起きない限り、そのようなコンフリクトは避けられます。

マイグレーション自体は、データベースに与える変更を定義したchangeメソッドの集まりです。リスト6.2の場合、changeメソッドはcreate_tableというRailsのメソッドを呼び、ユーザーを保存するためのテーブルをデータベースに作成します。create_tableメソッドはブロック変数を1つ持つブロック (4.3.2) を受け取ります。ここでは (“table”の頭文字を取って) tです。そのブロックの中で、create_tableメソッドはtオブジェクトを使って、今度はnameemailカラムをデータベースに作成します。型はいずれもstringです4。モデル名は単数形 (User) ですが、テーブル名は複数形 (users) です。これはRailsで用いられる言葉の慣習を反映しています。モデルはひとりのユーザーを表すのに対し、データベースのテーブルは複数のユーザーから構成されます。ブロックの最後の行t.timestampsは特別なコマンドで、created_atupdated_atという2つの「マジックカラム」を作成します。これらは、あるユーザーが作成または更新されたときに、その時刻を自動的に記録するタイムスタンプです (このマジックカラムの使用例を6.1.3から具体的に見ていきます)。 このマイグレーションで作られる完全なデータモデルを図6.2に示します。

user_model_initial
図6.2リスト6.2によるユーザーデータモデル

マイグレーションは、以下のようにrakeコマンド (コラム 2.1) を使って実行することができます。これを「マイグレーションの適用 (migrating up)」と呼びます。

$ bundle exec rake db:migrate

(2.2で、このコマンドを似たような状況で実行したことを思い出してみてください) 。初めてdb:migrateが実行されると、db/development.sqlite3という名前のファイルが生成されます。これはSQLite5データベースです。db/development.sqlite3ファイルを開くためのSQLite Database Browserという素晴らしいツールを使って、データベースの構造を詳しく参照することができます (図6.3)。図6.2の表と比べてみてください。図6.3の中にidというマイグレーションのときに説明されなかったカラムの存在に気づいたかもしれません。2.2で簡単に説明したとおり、このカラムは自動的に作成され、Railsが各行を一意に識別するために使用します。

sqlite_database_browser
図6.3SQLite Database Browserと作成したusersテーブル(拡大)

Railsチュートリアルで使用されているものすべてを含め、ほとんどのマイグレーションが可逆です。これは、db:rollbackというRakeタスクで変更を取り消せることを意味します。これを“マイグレーションの取り消し (migrate down)”と呼びます。

$ bundle exec rake db:rollback

(コラム 3.2では、マイグレーションを元に戻すための便利なテクニックを他にも紹介しています)。上のコマンドでは、データベースからusersテーブルを削除するためにdrop_tableコマンドを内部で呼び出しています。これがうまくいくのは、changeメソッドはdrop_tablecreate_tableの逆であることを知っているからです。つまり、ロールバック用の逆方向マイグレーションを簡単に導くことができるのです。あるカラムを削除するような不可逆なマイグレーションの場合は、changeメソッドの代わりに、updownのメソッドを別々に定義する必要があります。詳細については、Railsガイドの「Active Record マイグレーション」を参照してください。

もし今の時点でデータベースのロールバックを実行していた場合は、先に進む前にもう一度以下のようにマイグレーションを適用して元に戻してください。

$ bundle exec rake db:migrate

6.1.2modelファイル

これまで、リスト6.1のUserモデルの作成によってどのように (リスト6.2の) マイグレーションファイルが作成されるかを見てきました。そして図6.3でこのマイグレーションを実行した結果を見ました。usersテーブルを作成することで、development.sqlite3という名のファイルを更新し、idnameemailcreated_atupdated_atを作成しました。リスト6.1ではモデル自体も作成しました。この節では、以後これらを理解することに専念します。

まず、app/models/ディレクトリにあるuser.rbファイルに書かれたUserモデルのコードを見てみましょう。これは控えめに言ってもとてもよくまとまっています (リスト6.3)

リスト6.3 新しいUserモデル。
app/models/user.rb
class User < ActiveRecord::Base
end

4.4.2で行ったことを思い出してみましょう。class User < ActiveRecord::Baseという構文で、UserクラスはActiveRecord::Base継承するので、Userモデルは自動的にActiveRecord::Baseクラスのすべての機能を持ちます。もちろん、この継承の知識は、ActiveRecord::Baseに含まれるメソッドなどについて知らなければ何の役にも立ちません。それらの知識の一部についてこれから説明します。

6.1.3ユーザーオブジェクトを作成する

第4章と同じく、Railsコンソールを使用してデータモデルを調べてみましょう。現時点ではデータベースを変更したくないので、コンソールをサンドボックスモードで起動します。

$ rails console --sandbox
Loading development environment in sandbox
Any modifications you make will be rolled back on exit
>>

"Any modifications you make will be rolled back on exit" (ここで行ったすべての変更は終了時にロールバックされます) というメッセージにわかりやすく示されているように、コンソールをサンドボックスで起動すると、そのセッションで行ったデータベースへの変更をコンソールの終了時にすべて “ロールバック” (取り消し) してくれます。

4.4.5のコンソールセッションではUser.newで新しいユーザーオブジェクトを生成しましたが、リスト4.9のexample_userファイルを明示的にrequireするまでこのオブジェクトにはアクセスできませんでした。しかし、モデルを使うと状況は異なります。4.4.4で見たように、Railsコンソールは起動時にRailsの環境を自動的に読み込み、その環境にはモデルも含まれます。つまり、新しいユーザーオブジェクトを作成するときに余分な作業を行わずに済むということです。

>> User.new
=> #<User id: nil, name: nil, email: nil, created_at: nil, updated_at: nil>

上の出力は、ユーザーオブジェクトをコンソール用に出力したものです。

User.newを引数なしで呼んだ場合は、すべての属性がnilのオブジェクトを返します。4.4.5では、オブジェクトの属性を設定するための初期化ハッシュ (hash) を引数に取るように、Userクラスの例 (example_user.rb) を設計しました。この設計は、同様の方法でオブジェクトを初期化するActive Recordの設計に基づいています。

>> user = User.new(name: "Michael Hartl", email: "mhartl@example.com")
=> #<User id: nil, name: "Michael Hartl", email: "mhartl@example.com",
created_at: nil, updated_at: nil>

上のように、nameとemail属性が期待どおり設定されていることがわかります。

開発ログ (log/development.log) をtail -fしたまま上を実行していた場合、実行後に新しい行が何も表示されないことに気付いた方もいると思います。これは、User.newを実行しても単にRubyオブジェクトをメモリ上に作成するだけで、データベースにはアクセスしないためです。このユーザーオブジェクトをデータベースに実際に保存するには、user変数に対してsaveメソッドを呼びます。

>> user.save
=> true

saveメソッドは、成功すればtrueを、失敗すればfalseを返します (現状では、保存はすべて成功するはずです。失敗する場合については6.2で説明します)。 保存すると、SQLコマンドのINSERT INTO "users"という行が開発ログに追加出力されることがすぐに確認できます。Active Recordによって多数のメソッドが提供されているので、本書では生のSQLを書く必要がありません。従って、本書ではこれ以降はSQLコマンドについての説明を省略します。ただしそれでも、開発ログを監視することによってSQLについて多くのことを学ぶことができるでしょう。

作成した時点でのユーザーオブジェクトは、id属性、マジックカラムであるcreated_at属性とupdated_at属性の値がいずれもnilであったことを思い出してください。saveメソッドを実行した後に何が変更されたのかを確認してみましょう。

>> user
=> #<User id: 1, name: "Michael Hartl", email: "mhartl@example.com",
created_at: "2013-03-11 00:57:46", updated_at: "2013-03-11 00:57:46">

idには1という値が代入され、一方でマジックカラムには現在の日時が代入されているのがわかります6。現在、作成と更新のタイムスタンプは同一ですが、6.1.5では異なる値になります。

4.4.5のUserクラスと同様に、Userモデルのインスタンスはドット記法を用いてその属性にアクセスすることができます7

>> user.name
Michael Hartl
>> user.email
=> "mhartl@example.com"
>> user.updated_at
=> Mon, 11 Mar 2013 00:57:46 UTC +00:00

詳細は第7章でも説明しますが、上で見たようにモデルの生成と保存を2つのステップに分けておくと何かと便利です。しかし、Active RecordではUser.createでモデルの生成と保存を同時におこなう方法も提供されています。

>> User.create(name: "A Nother", email: "another@example.org")
#<User id: 2, name: "A Nother", email: "another@example.org", created_at:
"2013-03-11 01:05:24", updated_at: "2013-03-11 01:05:24">
>> foo = User.create(name: "Foo", email: "foo@bar.com")
#<User id: 3, name: "Foo", email: "foo@bar.com", created_at: "2013-03-11
01:05:42", updated_at: "2013-03-11 01:05:42">

User.createは、truefalseを返す代わりに、ユーザーオブジェクト自身を返すことに注目してください。返されたユーザーオブジェクトは (上の2つ目のコマンドにあるfooのように) 変数に代入することもできます。

destroycreateの逆です。

>> foo.destroy
=> #<User id: 3, name: "Foo", email: "foo@bar.com", created_at: "2013-03-11
01:05:42", updated_at: "2013-03-11 01:05:42">

奇妙なことに、destroycreateと同じようにそのオブジェクト自身を返しますが、その返り値を使用しても、もう一度destroyを呼ぶことはできません。そして、おそらくさらに奇妙なことに、destroyされたオブジェクトは以下のようにまだメモリ上に残っています。

>> foo
=> #<User id: 3, name: "Foo", email: "foo@bar.com", created_at: "2013-03-11
01:05:42", updated_at: "2013-03-11 01:05:42">

オブジェクトが本当に削除されたかどうかをどのようにして知ればよいでしょうか。そして、保存して削除されていないオブジェクトの場合、どうやってデータベースからユーザーを取得するのでしょうか。このあたりで、Active Recordでユーザーオブジェクトを検索する方法を学んでみましょう。

6.1.4ユーザーオブジェクトを検索する

Active Recordには、オブジェクトを検索するための方法がいくつもあります。これらの機能を使用して、過去に作成した最初のユーザーを探してみましょう。また、3番目のユーザー (foo) が削除されていることを確認しましょう。まずは存在するユーザーから探してみましょう。

>> User.find(1)
=> #<User id: 1, name: "Michael Hartl", email: "mhartl@example.com",
created_at: "2013-03-11 00:57:46", updated_at: "2013-03-11 00:57:46">

ここでは、User.findにユーザーのidを渡しています。その結果、Active Recordはそのidのユーザーを返します。

次に、id=3のユーザーがまだデータベースに存在するかどうかを確認してみましょう。

>> User.find(3)
ActiveRecord::RecordNotFound: Couldn't find User with ID=3

6.1.3で3番目のユーザーを削除したので、Active Recordはこのユーザーをデータベースの中から見つけることができませんでした。代わりに、findメソッドは例外 (exception) を発生します。例外はプログラムの実行時に何か例外的なイベントが発生したことを示すために使われます。この場合、存在しないActive Recordのidによって、findActiveRecord::RecordNotFound例外8が発生しました。

一般的なfindメソッド以外に、Active Recordには特定の属性でユーザーを検索する方法もあります。

>> User.find_by_email("mhartl@example.com")
=> #<User id: 1, name: "Michael Hartl", email: "mhartl@example.com",
created_at: "2013-03-11 00:57:46", updated_at: "2013-03-11 00:57:46">

find_by_emailは、usersテーブルのemail属性に基づいてActive Recordが自動的に生成するメソッドです (ご想像どおり、Active Recordはfind_by_nameというメソッドも自動的に生成します)。 Rails 4.0以降では、属性を検索する場合には上のメソッドに代えてより普遍性の高いfind_byメソッドを使用することが推奨されています。このメソッドでは属性をハッシュ形式で渡します。

>> User.find_by(email: "mhartl@example.com")
=> #<User id: 1, name: "Michael Hartl", email: "mhartl@example.com",
created_at: "2013-03-11 00:57:46", updated_at: "2013-03-11 00:57:46">

これまでメールアドレスをユーザー名として使用してきたので、このようなfind関連メソッドは、ユーザーをサイトにログインさせる方法を学ぶときに役に立ちます (第7章)。ユーザー数が膨大になるとfind_byでは検索効率が低下するのではないかと心配する方もいるかもしれませんが、あせる必要はありません。この問題およびデータベースのインデックスを使った解決策については6.2.5で扱います。

ユーザーを検索する一般的な方法をあと少しだけご紹介して、この節を終わりにすることにしましょう。まず初めにfirstメソッドです。

>> User.first
=> #<User id: 1, name: "Michael Hartl", email: "mhartl@example.com",
created_at: "2013-03-11 00:57:46", updated_at: "2013-03-11 00:57:46">

読んで字のごとく、firstは単にデータベースの最初のユーザーを返します。次はallメソッドです。

>> User.all
=> [#<User id: 1, name: "Michael Hartl", email: "mhartl@example.com",
created_at: "2013-03-11 00:57:46", updated_at: "2013-03-11 00:57:46">,
#<User id: 2, name: "A Nother", email: "another@example.org", created_at:
"2013-03-11 01:05:24", updated_at: "2013-03-11 01:05:24">]

期待どおり、allはデータベースのすべてのユーザーの配列 (4.3.1) を返します。

6.1.5ユーザーオブジェクトを更新する

いったんオブジェクトを作成すれば、今度は何度でも更新したくなるものです。基本的な更新の方法は2つです。ひとつは、4.4.5でやったように属性を個別に代入する方法です。

>> user           # ユーザーの属性を保持している
=> #<User id: 1, name: "Michael Hartl", email: "mhartl@example.com",
created_at: "2013-03-11 00:57:46", updated_at: "2013-03-11 00:57:46">
>> user.email = "mhartl@example.net"
=> "mhartl@example.net"
>> user.save
=> true

変更をデータベースに保存するために最後にsaveを実行する必要があることを忘れないでください。保存を行わずにreloadを実行すると、データベースの情報を元にオブジェクトを再読み込みするので、以下のように変更が取り消されます。

>> user.email
=> "mhartl@example.net"
>> user.email = "foo@bar.com"
=> "foo@bar.com"
>> user.reload.email
=> "mhartl@example.net"

今ユーザーを更新しました。6.1.3で約束したように、マジックカラムの更新日時が更新されました。

>> user.created_at
=> "2013-03-11 00:57:46"
>> user.updated_at
=> "2013-03-11 01:37:32"

属性を更新するもうひとつの方法は、update_attributesを使うものです。

>> user.update_attributes(name: "The Dude", email: "dude@abides.org")
=> true
>> user.name
=> "The Dude"
>> user.email
=> "dude@abides.org"

update_attributesメソッドは属性のハッシュを受け取り、成功時には更新と保存を続けて同時に行います (保存に成功した場合はtrueを返します)。ただし、検証に1つでも失敗すると、update_attributesの呼び出しは失敗します。たとえば、6.3で実装する、パスワードをレコードに保存することを要求すると検証は失敗します。特定の属性のみを更新する必要がある場合は、以下のようにupdate_attributeを単発で使用して制限を回避する必要があります。

>> user.update_attribute(:name, "The Dude")
=> true
>> user.name
=> "The Dude"

6.2ユーザーを検証する

ついに、6.1で作成したUserモデルに、アクセス可能なnameemail属性が与えられました。しかし、これらの属性はどんな値でも取ることができてしまいます。現在は (空文字を含む) あらゆる文字列が有効です。名前とメールアドレスには、もう少し何らかの制限があってよいはずです。たとえば、nameは空であってはならず、emailはメールアドレスのフォーマットに従う必要があります。さらに、メールアドレスをユーザーがログインするときの一意のユーザー名として使おうとしているので、メールアドレスがデータベース内で重複することのないようにする必要もあります。

要するに、nameemailにあらゆる文字列を許すのは避けるべきです。これらの属性値には、何らかの制約を与える必要があります。Active Recordでは検証 (バリデーション: validation) を使用してそのような制約を与えることができます。ここでは、よく使われるケースのうちのいくつかについて説明します。それらは存在性 (presence)の検証、長さ (length)の検証、フォーマット (format)の検証、一意性 (uniqueness)の検証です。6.3.4では、よく使われる最終検証として確認 (confirmation)を追加します。7.3では、ユーザーが制約に違反したときに、検証機能によって自動的に表示される有用なエラーメッセージをお見せします。

6.2.1最初のユーザーテスト

サンプルアプリケーションの他の機能と同様、Userモデルへの検証の追加もテスト駆動開発 (TDD) で行います。今回はUserモデルを作成したときに

--no-test-framework

(リスト5.29の例とは異なり) 上のフラグを渡さなかったので、リスト6.1のコマンドでは、モデル作成時に、ユーザーをテストするための初期specも同時に生成しています。ただし、生成された初期specは、実質的には空の状態です (リスト6.4)。

リスト6.4 実質的に空になっているデフォルトのUser spec。
spec/models/user_spec.rb
require 'spec_helper'

describe User do
  pending "add some examples to (or delete) #{__FILE__}"
end

上のコードではpendingメソッドだけが置かれており、何か意味のあるコードでspecを埋めるように促しています。このコードの効果は、空のテスト用データベースを用意してUserモデルのspecを実行することで確認できます。

$ bundle exec rake db:migrate
$ bundle exec rake db:test:prepare
$ bundle exec rspec spec/models/user_spec.rb
*


Finished in 0.01999 seconds
1 example, 0 failures, 1 pending

Pending:
  User add some examples to (or delete)
  /Users/mhartl/rails_projects/sample_app/spec/models/user_spec.rb
  (Not Yet Implemented)

多くのシステムでは、pendingのspecはコマンドライン上で黄色で表示されます。黄色は、成功 (緑) と失敗 (赤) の中間を意味します。

環境を整えるためにテスト環境用データベースを作成するコマンドを実行するのはこれが初めてです。

$ bundle exec rake db:test:prepare

上のコマンドは、単に開発データベースのデータモデルdb/development.sqlite3がテストデータベースdb/test.sqlite3に反映されるようにするものです。マイグレーションの後でたまにRakeタスクが実行できなくなることがあり、多くの人がこれに戸惑います。さらに、テストデータベースはたまに壊れることがあるので、その場合はリセットが必要です。もしテストスイートが理由もなく壊れるようなことがあれば、rake db:test:prepareを実行して、この問題が解決するか確認してみてください。

デフォルトのspecのアドバイスに従い、リスト6.5に示したいくつかのRSpecの例に置き換えてみましょう。

リスト6.5 :name:email属性のテスト。
spec/models/user_spec.rb
require 'spec_helper'

describe User do

  before { @user = User.new(name: "Example User", email: "user@example.com") }

  subject { @user }

  it { should respond_to(:name) }
  it { should respond_to(:email) }
end

リスト5.28でも使用したbeforeブロックは前処理用で、各サンプルが実行される前にそのブロックの中のコードを実行します。この場合、User.newと初期化用の有効なハッシュを使って、新しい@userインスタンス変数を作成します。そして以下のコードは、

subject { @user }

5.3.4page変数を扱ったときと同じように、@userをテストサンプルのデフォルトのsubjectとして設定します。

リスト6.5の2つのサンプルは、name属性とemail属性の存在をテストします。

it { should respond_to(:name) }
it { should respond_to(:email) }

Userオブジェクトがname属性を持っていない場合、beforeブロックの中で例外を投げるので、一見、これらのテストが冗長に思えるかもしれません。しかし、これらのテストを追加することで、user.nameuser.emailが正しく動作することを保証できます (beforeブロック内では、ただ属性のハッシュをnewに渡せるかどうかをテストしているだけです)。また、モデルの属性をテストすることは良い習慣です。なぜなら、モデルの属性についてテストをすることで、そのモデルが応答すべきメソッドの一覧が一目で分かるからです。 そして、このテストで使われているrespond_toメソッドは、Rubyのrespond_to?メソッドを暗黙的に使っています。respond_to?メソッドは、シンボルを1つ引数として受け取り、そのシンボルが表すメソッドまたは属性に対して、オブジェクトが応答する場合はtrueを返し、応答しない場合はfalseを返します。

$ rails console --sandbox
>> user = User.new
>> user.respond_to?(:name)
=> true
>> user.respond_to?(:foobar)
=> false

(4.2.3でも説明したとおり、Rubyではtrue/falseの真偽値を返すメソッド名の末尾に?記号を置く慣習があることを思い出してください)。これらのテストは、RSpecで使われる論理値の慣習に依存しています。以下のコードは、

@user.respond_to?(:name)

以下のRSpecのコードでテストできます。

it "should respond to 'name'" do
  expect(@user).to respond_to(:name)
end

ここでは、subject { @user }と記述してあるので、5.3.4で紹介したときと同様、@userを使わずに以下のように書くことができます。

it { should respond_to(:name) }

このようなテストが行えるので、新しい属性やメソッドをUserモデルに一時的に追加してテスト駆動開発を行うことができます。さらに、すべてのUserオブジェクトがこれらのメソッドに応答する必要があるという仕様もここで明らかになりました。

rake db:test:prepareを実行してテスト環境用データベースを用意したので、テストはパスするはずです。

$ bundle exec rspec spec/

6.2.2プレゼンスを検証する

おそらく最も基本的な検証 (validation) はプレゼンス (存在性) です。これは単に、与えられた属性が存在することを検証します。たとえばこの節では、ユーザーがデータベースに保存される前にnameとemailフィールドの両方が存在することを保証します。7.3.3では、この要求を新しいユーザーを作るためのユーザー登録フォームにまで徹底させる方法を確認します。

最初にname属性の存在を確認するテストを行いましょう。テスト駆動開発の最初のステップは失敗するテスト (3.2.1) を書くことですが、今回は、適切なテストを書くための検証事項についてまだ十分に理解していないので、まず最初に検証を書きます。検証については、コンソールを使って理解することにします。次に、その検証をコメントアウトし、失敗するテストを書き、そして検証のコメントアウトを解除することで、そのテストをパスさせられるかどうかを確認します。この手続きは、このような単純なテストでは、大げさで気取ったものに感じられるかもしれません。しかし著者はこれまでに、実際には見当違いなことをテストしている「単純な」テストを山ほど見てきました。テスト駆動開発を慎重に進めることは、結局は「私たちが正しい事項をテストしている」という自信を得る為の唯一の方法なのです。(上で紹介したコメントアウトのテクニックは、コードはあってもテストがどこにもないようなひどいアプリケーションを急いで救出するときにも役に立ちます)。

name属性の存在を検査する方法は、リスト6.6に示したとおり、validatesメソッドにpresence: trueという引数を与えて使うことです。presence: trueという引数は、要素がひとつのオプションハッシュです。4.3.4のようにメソッドの最後の引数としてハッシュを渡す場合、波括弧を付けなくても問題ありません (5.1.1でも説明したように、Railsのオプションハッシュは繰り返し登場するテーマです)。

リスト6.6 name属性の存在を検証する。
app/models/user.rb
class User < ActiveRecord::Base
  validates :name, presence: true
end

リスト6.6は一見魔法のように見えるかもしれませんが、validatesは単なるメソッドです。括弧を使用してリスト6.6を同等のコードに書き換えたものを以下に示します。

class User < ActiveRecord::Base
  validates(:name, presence: true)
end

コンソールを起動して、Userモデルに検証を追加した効果を見てみましょう9

$ rails console --sandbox
>> user = User.new(name: "", email: "mhartl@example.com")
>> user.save
=> false
>> user.valid?
=> false

user.savefalseを返しました。これは保存に失敗したことを意味します。最後のコマンドは、valid?メソッドで、オブジェクトがひとつ以上の検証に失敗したときにfalseを返します。すべての検証がパスした場合はtrueを返します。今回の場合、検証がひとつしかないので、どの検証が失敗したかわかります。しかし、失敗したときに作られるerrorsオブジェクトを使って確認すれば、さらに便利です。

>> user.errors.full_messages
=> ["Name can't be blank"]

(このエラーメッセージから、Railsが属性の存在性を検査するときにblank?メソッド (4.4.3の終わりに登場) を使用していることが推察できます。)

次は失敗するテストです。最初に、テストが失敗することを確認するために、この時点 (リスト6.7) でいったん検証をコメントアウトしてみましょう。

リスト6.7 失敗するテストを確認するために検証をコメントアウトする。
app/models/user.rb
class User < ActiveRecord::Base
  # validates :name, presence: true
end

最初の段階の検証テストをリスト6.8に示します。

リスト6.8 name属性の検証に対する、失敗するテスト。
spec/models/user_spec.rb
require 'spec_helper'

describe User do

  before do
    @user = User.new(name: "Example User", email: "user@example.com")
  end

  subject { @user }

  it { should respond_to(:name) }
  it { should respond_to(:email) }

  it { should be_valid }

  describe "when name is not present" do
    before { @user.name = " " }
    it { should_not be_valid }
  end
end

1番目の新しいテスト例は、単なる健全性チェックです。これを使用して、まず@userというsubjectが有効かどうかを確認します。

it { should be_valid }

上のコードは6.2.1で見た別のRSpecの真偽値の慣習を示すサンプルです。あるオブジェクトが、真偽値を返すfoo?というメソッドに応答するのであれば、それに対応するbe_fooというテストメソッドが (自動的に) 存在します。この場合、以下のメソッド呼び出しの結果をテストすることができます。

@user.valid?

上の呼び出しの結果を、以下のコードでテストできます。

it "should be valid" do
  expect(@user).to be_valid
end

さっきと同様にsubject { @user }があるので、上のコードは@userを使わずに以下のように書くことができます。

it { should be_valid }

2番目のテストは、まずユーザーのnameに無効な値 (blank) を設定し、@userオブジェクトの結果も無効になることをテストして確認します。

describe "when name is not present" do
  before { @user.name = " " }
  it { should_not be_valid }
end

ユーザーのnameに無効な値 (blank) を設定するにはbeforeブロックを使います。次にユーザーオブジェクトの結果が無効であることを確認します。

この時点でテストが失敗することを確認してください。

$ bundle exec rspec spec/models/user_spec.rb
...F
4 examples, 1 failure

それではここで、テストにパスするために検証部分のコメントアウトを解除しましょう (つまり、リスト6.7リスト6.6に戻します)。

$ bundle exec rspec spec/models/user_spec.rb
....
4 examples, 0 failures

もちろん、今度はメールアドレスの存在性も検証しましょう。このテスト (リスト6.9) は、name属性のテストと似ています。

リスト6.9 email属性の存在性のテスト。
spec/models/user_spec.rb
require 'spec_helper'

describe User do

  before do
    @user = User.new(name: "Example User", email: "user@example.com")
  end
  .
  .
  .
  describe "when email is not present" do
    before { @user.email = " " }
    it { should_not be_valid }
  end
end

以下のリスト6.10に示すように、メールアドレス検証の実装も名前の検証と実質的に同じです。

リスト6.10 name属性とemail属性の存在性を検証する。
app/models/user.rb
class User < ActiveRecord::Base
  validates :name,  presence: true
  validates :email, presence: true
end

これですべてのテストにパスするはずです。これで、存在性の検証は完成しました。

6.2.3長さを検証する

各ユーザーは、Userモデル上に名前を持つことを強制されるようになりました。しかし、これだけでは十分ではありません。ユーザーの名前はサンプルWebサイトに表示されるものなので、名前の長さにも制限を与える必要があります。6.2.2で既に同じような作業を行ったので、この実装は簡単です。

まずはテストを作成します。最長のユーザー名の長さに科学的な根拠はありませんので、単に50という上限として手頃な値を使うことにします。つまりここでは、51文字の名前は長すぎることを検証します (リスト6.11)。

リスト6.11 nameの長さ検証のテスト。
spec/models/user_spec.rb
require 'spec_helper'

describe User do

  before do
    @user = User.new(name: "Example User", email: "user@example.com")
  end
  .
  .
  .
  describe "when name is too long" do
    before { @user.name = "a" * 51 }
    it { should_not be_valid }
  end
end

リスト6.11では、51文字の文字列を簡単に作るために “文字列のかけ算” を使いました。結果をコンソール上で確認できます。

>> "a" * 51
=> "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
>> ("a" * 51).length
=> 51

今の時点ではリスト6.11のテストは失敗するはずです。これをパスさせるためには、長さを強制するための検証の引数について知っておく必要があります。:maximumパラメータと共に用いられる:lengthは、長さの上限を強制します (リスト6.12)。

リスト6.12 name属性の長さの検証を追加する。
app/models/user.rb
class User < ActiveRecord::Base
  validates :name,  presence: true, length: { maximum: 50 }
  validates :email, presence: true
end

これでテストにパスするはずです。パスしたテストスイートを流用して、今度は少し難しい、メールアドレスのフォーマット検証作業に取りかかりましょう。

6.2.4フォーマットを検証する

name属性の検証には、空文字でない、名前が51文字未満であるという最小限の制約しか与えていませんでした。email属性の場合は、もっと厳重な要求を満たさなければなりません。これまでは空のメールアドレスのみを禁止してきましたが、ここではメールアドレスにおなじみのパターンuser@example.comに合っているかどうかも確認することを要求します。

なお、ここで使用するテストや検証は、形式がひとまず有効なメールアドレスを受け入れ、形式があからさまに無効なものを拒否するだけであり、すべての場合を網羅したものではないという点に注意してください。最初に、有効なメールアドレスと無効なメールアドレスのコレクションに対するテストを行いましょう。このコレクションを作るために、以下のコンソールセッションに示したような、文字列の配列を簡単に作れる%w[]という便利なテクニックを知っておくと良いでしょう。

>> %w[foo bar baz]
=> ["foo", "bar", "baz"]
>> addresses = %w[user@foo.COM THE_US-ER@foo.bar.org first.last@foo.jp]
=> ["user@foo.COM", "THE_US-ER@foo.bar.org", "first.last@foo.jp"]
>> addresses.each do |address|
?>   puts address
>> end
user@foo.COM
THE_US-ER@foo.bar.org
first.last@foo.jp

eachメソッドを使ってaddresses配列の各要素を繰り返し取り出しました (4.3.2)。このテクニックを学んだことで、基本となるメールアドレスフォーマット検証のテストを書く準備が整いました (リスト6.13)。

リスト6.13 メールアドレスフォーマットの検証テスト。
spec/models/user_spec.rb
require 'spec_helper'

describe User do

  before do
    @user = User.new(name: "Example User", email: "user@example.com")
  end
  .
  .
  .
  describe "when email format is invalid" do
    it "should be invalid" do
      addresses = %w[user@foo,com user_at_foo.org example.user@foo.
                     foo@bar_baz.com foo@bar+baz.com]
      addresses.each do |invalid_address|
        @user.email = invalid_address
        expect(@user).not_to be_valid
      end
    end
  end

  describe "when email format is valid" do
    it "should be valid" do
      addresses = %w[user@foo.COM A_US-ER@f.b.org frst.lst@foo.jp a+b@baz.cn]
      addresses.each do |valid_address|
        @user.email = valid_address
        expect(@user).to be_valid
      end
    end
  end
end

既に述べたとおり、上のテストはすべてを盛り込んだものではありませんが、一般的に有効なメールアドレスの形式であるuser@foo.COMTHE_US-ER@foo.bar.org (大文字、アンダースコア、複合ドメイン) 、first.last@foo.jp (一般的な企業のユーザー名「名.姓」と、2文字のトップレベルドメイン「jp」) を、いくつかの無効な形式と共に確認します。

メールアドレスのフォーマット検証を行うアプリケーションコードでは、validatesメソッドの:format引数に、フォーマットを定義するための正規表現 (regular expression) (regexとも書きます) を与えます (リスト6.14)。

リスト6.14 正規表現を使ったメールアドレスフォーマットの検証。
app/models/user.rb
class User < ActiveRecord::Base
  validates :name,  presence: true, length: { maximum: 50 }
  VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i
  validates :email, presence: true, format: { with: VALID_EMAIL_REGEX }
end

正規表現VALID_EMAIL_REGEX定数です。大文字で始まる名前はRubyでは定数を意味します。以下のコードは、

  VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i
  validates :email, presence: true, format: { with: VALID_EMAIL_REGEX }

このパターンに一致するメールアドレスだけが有効であることをチェックします (VALID_EMAIL_REGEXは大文字で始まるので、Rubyの定数として扱われ、値は変更できません)。

ところで、この正規表現パターンはどうやって作ればよいのでしょうか。正規表現は簡潔な (読めないという人もいますが) テキストパターンマッチング言語から成ります。正規表現を組み立てることを学ぶのはそれだけでひとつの技術分野であり、手短に説明するのは簡単ではありませんが、ともあれ最初の説明のためにVALID_EMAIL_REGEXをビットサイズの部品に分解しました (表6.1)10。本当に正規表現を学びたいなら、素晴らしい正規表現エディタであるRubular (図6.4) が必要不可欠です11。RubularのWebサイトは、正規表現を作るための美しく対話的なインターフェイスを持っています。また、手軽な正規表現のクイックリファレンスにもなります。Rubularのサイトをブラウザで開き、表6.1の表現をひとつずつ入力して結果を確かめながら正規表現を学ぶことをぜひともお勧めします。正規表現について学んだことがなくても、Rubularを使えば2〜3時間ほどで慣れることができます (: リスト6.14の正規表現をRubularで使う場合、冒頭の\Aと末尾の\zの文字は含めないでください)。

表現意味
/\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i(完全な正規表現)
/正規表現の開始を示す
\A文字列の先頭
[\w+\-.]+英数字、アンダースコア (_)、プラス (+)、ハイフン (-)、ドット (.) のいずれかを少なくとも1文字以上繰り返す
@アットマーク
[a-z\d\-.]+英小文字、数字、ハイフン、ドットのいずれかを少なくとも1文字以上繰り返す
\.ドット
[a-z]+英小文字を少なくとも1文字以上繰り返す
\z文字列の末尾
/正規表現の終わりを示す
i大文字小文字を無視するオプション
表6.1リスト6.14のメールの正規表現の分解。

ところで、公式標準によるとメールアドレスに完全に一致する正規表現は存在するのだそうです。しかし、苦労して導入するほどの甲斐はありません。リスト6.14の例は問題なく動作しますし、おそらく公式のものより良いでしょう12。上の正規表現には少しだけ残念な点があります。foo@bar..comのようなドットの連続を誤りとして検出できません。この問題の修正は、演習問題に回します (6.5)。

rubular
図6.4素晴らしい正規表現エディタRubular(拡大)

これでテストはすべてパスするはずです(実際、この有効なメールアドレスのテストはこれまでいつもパスしてきました。正規表現のプログラミングは間違いが起こりやすいことで有名なので、ここで行なっている有効なメールアドレスのテストは、主としてVALID_EMAIL_REGEXに対する形式的な健全性チェックに過ぎません)。残る制約は、メールアドレスが一意であることを強制するものだけとなりました。

6.2.5一意性を検証する

メールアドレスの一意性を強制するために (ユーザー名として使うために)、validatesメソッドの:uniqueオプションを使います。ただしここで重大な警告があります。以下の文面は流し読みせず、必ず注意深く読んでください。

今回もいつものようにテストを作成するところから始めます。モデルのテストではこれまで、主にUser.newを使ってきました。このメソッドは単にメモリ上にRubyのオブジェクトを作るだけです。しかし、一意性のテストのためには、メモリ上だけではなく、実際にレコードをデータベースに登録する必要があります13。(最初の段階の) 重複メールアドレスのテストをリスト6.15に示します。

リスト6.15 重複するメールアドレスの拒否のテスト。
spec/models/user_spec.rb
require 'spec_helper'

describe User do

  before do
    @user = User.new(name: "Example User", email: "user@example.com")
  end
  .
  .
  .
  describe "when email address is already taken" do
    before do
      user_with_same_email = @user.dup
      user_with_same_email.save
    end

    it { should_not be_valid }
  end
end

上のコードは、@userと同じメールアドレスのユーザーを事前に作成する手法です。今回は、同じ属性のユーザーを作るために、@user.dupを使っています。同じ属性のユーザーが保存された後では、元の@userと同じメールアドレスが既にデータベース内に存在しているため、@userは無効になります。

先ほどのリスト6.15のテストは、リスト6.16のコードを用いてパスさせることができます。

リスト6.16 メールアドレスの一意性の検査。
app/models/user.rb
class User < ActiveRecord::Base
  .
  .
  .
  validates :email, presence: true, format: { with: VALID_EMAIL_REGEX },
                    uniqueness: true
end

実装の途中ですが、ここでひとつ補足します。通常、メールアドレスでは大文字小文字が区別されません。すなわち、foo@bar.comFOO@BAR.COMFoO@BAr.coMと書いても扱いは同じです。従って、メールアドレスの検証ではこのような場合も考慮する必要があります14リスト6.17のコードでは、大文字小文字を区別せずにテストしています。

リスト6.17 大文字小文字を区別しない、重複するメールアドレスの拒否のテスト。
spec/models/user_spec.rb
require 'spec_helper'

describe User do

  before do
    @user = User.new(name: "Example User", email: "user@example.com")
  end
  .
  .
  .
  describe "when email address is already taken" do
    before do
      user_with_same_email = @user.dup
      user_with_same_email.email = @user.email.upcase
      user_with_same_email.save
    end

    it { should_not be_valid }
  end
end

上のコードではStringのupcaseメソッドを使っています (4.3.2)。このテストは最初のメールアドレスの重複テストと同じことをしていますが、大文字に変換したメールアドレスを使っている点が異なります。もしこのテストが少し抽象的すぎると感じるなら、Railsコンソールを起動して確認しましょう。

$ rails console --sandbox
>> user = User.create(name: "Example User", email: "user@example.com")
>> user.email.upcase
=> "USER@EXAMPLE.COM"
>> user_with_same_email = user.dup
>> user_with_same_email.email = user.email.upcase
>> user_with_same_email.valid?
=> true

現在の一意性検証では大文字小文字を区別しているため、user_with_same_email.valid?trueになります。しかし、ここではfalseになる必要があります。幸い、:uniquenessでは:case_sensitiveといううってつけのオプションが使用できます (リスト6.18)。

リスト6.18 メールアドレスの大文字小文字を無視した一意性の検証
app/models/user.rb
class User < ActiveRecord::Base
  .
  .
  .
  validates :email, presence: true, format: { with: VALID_EMAIL_REGEX },
                    uniqueness: { case_sensitive: false }
end

上のコードでは、単にtruecase_sensitive: falseで置き換えただけであることに注目してください。Railsはこの場合、:uniquenesstrueと判断します。この時点で、アプリケーションは重要な警告と共にメールアドレスの一意性を強制し、テストスイートもパスするはずです。

一意性の警告

上で示した警告には、1つ小さな問題があります。

validates :uniquenessを使用しても、一意性は保証されません。

えっ?!いったい何が問題なのでしょうか。以下のシナリオを見てください。

  1. アリスはサンプルアプリケーションにユーザー登録します。メールアドレスはalice@wonderland.comです。
  2. アリスは誤って “Submit” を素早く2回クリックしてしまいます。そのためリクエストが2つ連続で送信されます。
  3. 次のようなことが順に発生します。リクエスト1は、検証にパスするユーザーをメモリー上に作成します。リクエスト2でも同じことが起きます。リクエスト1のユーザーが保存され、リクエスト2のユーザーも保存されます。
  4. この結果、一意性の検証が行われているにもかかわらず、同じメールアドレスを持つ2つのユーザーレコードが作成されてしまいます。

上のシナリオが信じがたいもののように思えるかもしれませんが、どうか信じてください。RailsのWebサイトでは、トラフィックが多いときにこのような問題が発生する可能性があるのです。幸い、解決策の実装は簡単です。実は、この問題はデータベースレベルでも一意性を強制するだけで解決します。具体的には、emailカラムにデータベースのインデックスを作成し、そのインデックスが一意であることを要求します。

emailインデックスを追加すると、データモデリングの変更が必要になります。Railsでは (6.1.1で見たように) マイグレーションでインデックスを追加します。6.1.1で、Userモデルを生成すると自動的に新しいマイグレーションが作成されたことを思い出してください (リスト6.2)。今回の場合は、既に存在するモデルに構造を追加するので、以下のようにmigrationジェネレーターを使用してマイグレーションを直接作成する必要があります。

$ rails generate migration add_index_to_users_email

ユーザー用のマイグレーションと異なり、メールアドレスの一意性のマイグレーションは未定義になっています。リスト6.19のように定義を記述する必要があります15

リスト6.19 メールアドレスの一意性を強制するためのマイグレーション。
db/migrate/[timestamp]_add_index_to_users_email.rb
class AddIndexToUsersEmail < ActiveRecord::Migration
  def change
    add_index :users, :email, unique: true
  end
end

上のコードでは、usersテーブルのemailカラムにインデックスを追加するためにadd_indexというRailsのメソッドを使っています。インデックス自体は一意性を強制しませんが、オプションでunique: trueを指定することで強制できるようになります。

最後に、データベースをマイグレートします。

$ bundle exec rake db:migrate

(上のコマンドが失敗した場合は、実行中のサンドボックスのコンソールセッションを終了してみてください。そのセッションがデータベースをロックしてマイグレーションを妨げている可能性があります)。一意性を強制すると何が起きるかについて関心のある方は、db/schema.rbを開いてみると以下のような行があるはずです。

add_index "users", ["email"], name: "index_users_on_email", unique: true

残念なことに、メールアドレスの一意性を保証するためには、もう1つやらなければならないことがあります。それは、メールアドレスをデータベースに保存する前にすべての文字を小文字に変換することです。その理由は、データベースのアダプタが常に大文字小文字を区別するインデックスを使っているとは限らないからです16。これを行うにはコールバックというテクニックを利用します。コールバックとは、Active Recordオブジェクトが持続している間のどこかの時点で、Active Recordオブジェクトに呼び出してもらうメソッドです (Rails APIの「コールバックの登録ポイント (英語)」を参照してください)。今回の場合は、before_saveコールバックを使います。リスト6.20に示したように、ユーザーをデータベースに保存する前にemail属性を強制的に小文字に変換します。

リスト6.20 email属性を小文字に変換してメールアドレスの一意性を保証する。
app/models/user.rb
class User < ActiveRecord::Base
  before_save { self.email = email.downcase }
  .
  .
  .
end

リスト6.20のコードは、before_saveコールバックにブロックを渡してユーザーのメールアドレスを設定します。設定されるメールアドレスは、現在の値をStringクラスのdowncaseメソッドを使って小文字バージョンにしたものです。このコードは少し上級者向けなので、今はただ、このコードが動作することを信じてください。それでは気の済まない方は、リスト6.16から一意性の検証部分をコメントアウトし、重複したメールアドレスを持つユーザーを試しに作成してみれば、エラーが発生するはずです (このテクニックについては8.2.1でもう一度取り上げます。そこではお勧めのメソッド参照方法の慣習について説明します)。リスト6.20のコード用テストの作成は演習に回します (6.5)。

これで、先に述べたアリスのシナリオはうまくいくようになります。データベースは、最初のリクエストに基づいてユーザーのレコードを保存しますが、2度目の保存は一意性の制約に反するので拒否します (Railsのログにエラーが出力されますが、害は生じません。ここで発生したActiveRecord::StatementInvalid例外を実際にキャッチすることもできますが、このチュートリアルでは解説しません)。インデックスをemail属性に追加したことで、6.1.4で述べた2番目の目標 (エントリ多数の場合の検索効率向上) も達成されます。これは、find_byの効率の問題がインデックスによって解決されたためです (コラム 6.2)。

6.3セキュアなパスワードを追加する

この節では、ユーザーに最後の属性を追加します。セキュアパスワードは、サンプルアプリケーションでユーザーを認証するために使用します。セキュアパスワードという手法では、各ユーザーにパスワードとパスワードの確認を入力させ、それを (そのままではなく) 暗号化したものをデータベースに保存します。また、入力されたパスワードを使用してユーザーを認証する手段と、第8章で使用する、ユーザーがサイトにサインインできるようにする手段も提供します。

ユーザーの認証は、パスワードの送信、暗号化、データベース内の暗号化された値との比較という手順を踏みます。比較の結果が一致すれば、送信されたパスワードは正しいと認識され、そのユーザーは認証されます。ここで、生のパスワードではなく、暗号化されたパスワード同士を比較していることに注目してください。こうすることで、生のパスワードをデータベースに保存するという危険なことをしなくてもユーザーを認証できます。これで、仮にデータベースの内容が盗まれたり覗き見されるようなことがあっても、パスワードの安全性が保たれます。

セキュアなパスワードの実装は、has_secure_passwordというRailsのメソッドを呼び出すだけでほとんど終わってしまいます (このメソッドはRails 3.1から導入されました)。このメソッド1つだけでセキュアなパスワードの実装がほとんど終わってしまうので、逆にこの機能を最初から手作りするのは簡単ではありません。6.3.2以降では、has_secure_passwordメソッドを早期に導入しておき、テストを新しく書くたびにこのメソッドを一時的にコメントアウトして、正しいテスト駆動開発を行うことをお勧めします。(スクリーンキャストは、このような一からの手作り開発手順を解説するのに向いています。この課題を十分に理解したい方は「Ruby on Railsチュートリアルのスクリーンキャスト (日本語版あり)」を参照してください)。

6.3.1暗号化されたパスワード

最初に、ユーザーのデータモデルに必要な変更を行います。具体的には、usersテーブルにpassword_digestカラムを追加します (図6.5)。なお、digestという言葉は暗号学的ハッシュ関数が用語の語源です。6.3.4の実装が動作するには、カラム名を正確にpassword_digestとする必要があります。パスワードを適切に暗号化することで、たとえ攻撃者によってデータベースからパスワードをコピーされてもWebサイトにサインインされることのないようにできます。

user_model_password_digest
図6.5Userモデルにpassword_digest属性を追加する。

ハッシュ関数には最新のbcryptを使用し、パスワードを不可逆的に暗号化してパスワードハッシュを作成します。サンプルアプリケーションでbcryptを使用するために、bcrypt-ruby gemをGemfileに追加します (リスト6.21)。

リスト6.21 bcrypt-rubyGemfileに追加する。
source 'https://rubygems.org'
ruby '2.0.0'
#ruby-gemset=railstutorial_rails_4_0

gem 'rails', '4.0.5'
gem 'bootstrap-sass', '2.3.2.0'
gem 'sprockets', '2.11.0'
gem 'bcrypt-ruby', '3.1.2'
.
.
.

次にbundle installを実行します。

$ bundle install

ユーザーはpassword_digestカラムにアクセスしなければならないので、リスト6.22に示すように、ユーザーオブジェクトはpassword_digestに応答する必要があります。

リスト6.22 Userオブジェクトにpassword_digestカラムがあることを確認するテスト。
spec/models/user_spec.rb
require 'spec_helper'

describe User do

  before do
    @user = User.new(name: "Example User", email: "user@example.com")
  end

  subject { @user }

  it { should respond_to(:name) }
  it { should respond_to(:email) }
  it { should respond_to(:password_digest) }
  .
  .
  .
end

このテストがパスするには、最初にpassword_digestカラム用の適切なマイグレーションを生成します。

$ rails generate migration \
add_password_digest_to_users password_digest:string

上のコマンドの最初の引数はマイグレーション名、次の引数は作成する属性の名前と型です (リスト6.1で最初にusersテーブルを生成したときのマイグレーションと比較してみてください)。マイグレーション名は自由に指定できますが、上のように末尾を_to_usersにしておくことをお勧めします。こうしておくと、usersテーブルにカラムを追加するマイグレーションがRailsによって自動的に作成されるからです。また、上のコマンドに2番目の引数を与えることで、リスト6.23のように完全なマイグレーションを構成するための情報をRailsに与えることができます。

リスト6.23 password_digestカラムをusersテーブルに追加するマイグレーション。
db/migrate/[ts]_add_password_digest_to_users.rb
class AddPasswordDigestToUsers < ActiveRecord::Migration
  def change
    add_column :users, :password_digest, :string
  end
end

上のコードでは、add_columnメソッドを使用してpassword_digest カラムをusersテーブルに追加しています。

以下のように開発データベースをマイグレーションしてテストデータベースを準備することで、リスト6.22の失敗するテストをパスすることができます。

$ bundle exec rake db:migrate
$ bundle exec rake db:test:prepare
$ bundle exec rspec spec/

6.3.2パスワードと確認

図6.1のモックアップに示したように、ユーザーにパスワードを確認させるようにしましょう。パスワードの確認入力は、入力ミスを減らすためにWebで広く使用されています。パスワード確認の強制はコントローラの階層でも行うことができますが、モデルの中でActive Recordを使用して制限を与えるのが慣習になっています。そのためには、password属性とpassword_confirmation属性をUserモデルに追加し、レコードをデータベースに保存する前に2つの属性が一致するように要求します。これまでに使用した属性と異なり、パスワード関連の属性は「仮想」にする点に注意してください。つまり、これらの属性は一時的にメモリ上に置き、データベースには保存されないようにします。6.3.4でも説明しますが、これらの仮想属性はhas_secure_passwordでは自動的に実装されます。

最初に、respond_toを使用してパスワードとパスワードの確認をリスト6.24のようにテストします。

リスト6.24 password属性とpassword_confirmation属性をテストする。
spec/models/user_spec.rb
require 'spec_helper'

describe User do

  before do
    @user = User.new(name: "Example User", email: "user@example.com",
                     password: "foobar", password_confirmation: "foobar")
  end

  subject { @user }

  it { should respond_to(:name) }
  it { should respond_to(:email) }
  it { should respond_to(:password_digest) }
  it { should respond_to(:password) }
  it { should respond_to(:password_confirmation) }

  it { should be_valid }
  .
  .
  .
end

上のコードでは、以下のようにUser.newハッシュの初期化に:password:password_confirmationを追加していることに注目してください。

before do
  @user = User.new(name: "Example User", email: "user@example.com",
                   password: "foobar", password_confirmation: "foobar")
end

パスワードは空欄であってはならないので、パスワードの存在確認テストを別に追加します。

describe "when password is not present" do
  before do
    @user = User.new(name: "Example User", email: "user@example.com",
                     password: " ", password_confirmation: " ")
  end
  it { should_not be_valid }
end

パスワードの不一致テストはこのすぐ後に追加するので、上のコードではパスワードとパスワードの確認を両方とも空欄にすることでパスワードの存在確認テストを行なっています。

パスワードとパスワードの確認が一致するかどうかもテストする必要があります。パスワードが一致する場合については既にit { should be_valid }で確認できるので、次は以下のように不一致の場合のテストを追加します。

describe "when password doesn't match confirmation" do
  before { @user.password_confirmation = "mismatch" }
  it { should_not be_valid }
end

ここまでのすべてを盛り込んだ失敗するテストをリスト6.25に示します。

リスト6.25 パスワードとパスワードの確認をテストする。
spec/models/user_spec.rb
require 'spec_helper'

describe User do

  before do
    @user = User.new(name: "Example User", email: "user@example.com",
                     password: "foobar", password_confirmation: "foobar")
  end

  subject { @user }

  it { should respond_to(:name) }
  it { should respond_to(:email) }
  it { should respond_to(:password_digest) }
  it { should respond_to(:password) }
  it { should respond_to(:password_confirmation) }

  it { should be_valid }
  .
  .
  .
  describe "when password is not present" do
    before do
      @user = User.new(name: "Example User", email: "user@example.com",
                       password: " ", password_confirmation: " ")
    end
    it { should_not be_valid }
  end

  describe "when password doesn't match confirmation" do
    before { @user.password_confirmation = "mismatch" }
    it { should_not be_valid }
  end
end

上のリスト6.25のテストは、リスト6.26に示したようにコードにたった1行追加するだけでパスするようになります。

リスト6.26 最初のパスワードテストをパスするようにする。
app/models/user.rb
class User < ActiveRecord::Base
  .
  .
  .
  has_secure_password
end

以下の1行を追加するだけで、

has_secure_password

現在のパスワード関連テストがすべてパスするようになり、他の多くのテストにもパスするようになります。このメソッドがあまりに目覚しい働きをするので、この後のテストで赤から青に状態を変えるチュートリアルが逆にやりにくくなってしまいます。そこで、一時的にコメントアウトしておいてください (リスト6.27).

リスト6.27 テスト駆動開発用にhas_secure_passwordをコメントアウトする。
app/models/user.rb
class User < ActiveRecord::Base
  .
  .
  .
  # has_secure_password
end

6.3.3ユーザー認証

パスワード機構というパズルの最後のひとかけらは、ユーザーをメールアドレスとパスワードに基いて取得する手段です。この作業は2つに分けるのが自然です。最初に、ユーザーをメールアドレスで検索します。次に、受け取ったパスワードでユーザーを認証します。この節のテストは、最後の1つを除いてすべてhas_secure_passwordメソッドによって実装できるので、リスト6.27でコメントアウトした行は実装中にコメント解除し、テストがパスするようにしてください。

最初の手順の実装は簡単です。6.1.4でも説明したように、find_byメソッドを使用すれば、受け取ったメールアドレスでユーザーを検索できます。

user = User.find_by(email: email)

次の手順は、authenticateメソッドを使用して、受け取ったパスワードがユーザーのパスワードと一致することを確認します。第8章では、以下のようなコードを使用して現在の (サインインしている) ユーザーを取得する予定です。

current_user = user.authenticate(password)

受け取ったパスワードがユーザーのパスワードと一致するとユーザーが返され、一致しない場合はfalseが返されます。

これまで同様、RSpecを使用してauthenticateメソッドへの要求内容を表現することができます。ただし、このテストはこれまでよりも高度な内容になるため、いくつかに分割して説明します。RSpecが初めての方は、この節を繰り返し読んでみてください。最初に、Userオブジェクトがauthenticateに応答することを要求します。

it { should respond_to(:authenticate) }

次に、パスワードが一致する場合と一致しない場合についてそれぞれ記述します。

describe "return value of authenticate method" do
  before { @user.save }
  let(:found_user) { User.find_by(email: @user.email) }

  describe "with valid password" do
    it { should eq found_user.authenticate(@user.password) }
  end

  describe "with invalid password" do
    let(:user_for_invalid_password) { found_user.authenticate("invalid") }

    it { should_not eq user_for_invalid_password }
    specify { expect(user_for_invalid_password).to be_false }
  end
end

上のコードで、beforeブロックはユーザーをデータベースに事前に保存します。これにより、find_byメソッドが動作するようになります。このメソッドをletメソッドで以下のようにテストします。

let(:found_user) { User.find_by(email: @user.email) }

これまでいくつかの演習でletメソッドを使用してきましたが、今回のようにチュートリアルの本文で言及するのはこれが初めてです。letメソッドの詳細についてはコラム 6.3を参照してください。

2つのdescribeブロックでは、@userfound_userが一致する (パスワードが一致する) 場合と一致しない (パスワードが一致しない) 場合についてそれぞれテストします。コードで使用されているeqは、オブジェクト同士が同値であるかどうかを調べます (eqの内部では4.3.1で説明した二重等号演算子==を使用してオブジェクトが同値であるかどうかを確認しています)。以下のコードに注目してください。

describe "with invalid password" do
  let(:user_for_invalid_password) { found_user.authenticate("invalid") }

  it { should_not eq user_for_invalid_password }
  specify { expect(user_for_invalid_password).to be_false }
end

上のコードではletがもう一度使用されており、さらにspecifyというメソッドも使用されています。実は、このspecifyはitと同義であり、itを使用すると英語として不自然な場合にこれで代用することができます。この場合、「it should not equal wrong user」(itはユーザーなど) とするのは英語として自然ですが、「user: user with invalid password should be false」は不自然であり、「specify: user with invalid password should be false」とすれば自然になります。