Ruby on Rails チュートリアル

実例を使ってRailsを学ぼう

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

第3版 目次

前書き

私が前にいた会社 (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 創業者

謝辞

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 Bat、Geoffrey Grosenbach、Peter Cooper、Matt Aimonetti、Mark Bates、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 チュートリアル」という Web 開発を始めるときに最もよく参考にされる本の著者です。また、Softcover という自費出版プラットフォームの共同創業者でもあります。以前は、(今ではすっかり古くなってしまいましたが)「RailsSpace」という本の執筆および開発に携わったり、また、 一時人気を博した Ruby on Rails ベースのソーシャルネットワーキングプラットフォーム「Insoshi」の開発にも携わっていました。なお、2011年には、Rails コミュニティへの高い貢献が認められて、Ruby Hero Award を受賞しました。ハーバード大学卒業後、カリフォルニア工科大学物理学博士号を取得し、起業プログラム Y Combinator の卒業生でもあります。

  1. 3日間で読破するのは異常です! 実際にはもっと時間をかけて読むのが一般的です。

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

Ruby on Railsチュートリアル: RailsでWeb開発を学ぶ』へようこそ。本チュートリアルは、カスタムWebアプリケーションの開発方法を教えるために書かれました。そのためのツールとして、かの有名なRuby on RailsというWebフレームワークを採用しています。Web開発の初心者にとっては、このRuby on RailsチュートリアルがWeb開発のすべて (Rubyの基礎、HTMLとCSS、データベース、バージョン管理、開発技法など) を網羅した、Web開発者やIT起業家を目指す方向けのよい入門書となるでしょう。さらに、本チュートリアルはWeb開発のベテランにとっても有用です。MVCやREST、ジェネレータ、マイグレーション、ルーティング、ERBなど、Railsフレームワークのコア技術を本チュートリアルでまとめて学ぶことができます。いずれの場合であっても、Ruby on Railsチュートリアルを最後まで終えることができれば、Rails周辺のさらに高度な内容の書籍、ブログ、スクリーンキャストなどを読み解く力を身に付けられます1

Ruby on Railsチュートリアルでは、一貫したWeb開発技法を学ぶために 3 つのサンプルアプリケーションを作ります。helloアプリ (1.3)、もう少しだけ複雑なtoyアプリ (2)、実践的なsampleアプリ (3から12まで) です。アプリ名をあえて具体的にしていないことからわかるように、Ruby on Railsチュートリアルで開発するアプリケーションでは、特定のWebサービスに偏らない一般的な記述を心がけており、読者の目的にかかわらず本チュートリアルでWeb開発の基礎を学ぶことができます。とは言うものの、最終的なサンプルアプリケーションは (初期はRailsで実装されていた) 某ソーシャルマイクロブログサイトと「偶然にも」とてもよく似ていますが。

Ruby on RailsチュートリアルでWeb開発を学ぶうえでどんな基礎知識が必要なのか (=どのぐらい素人でも大丈夫なのか) という質問をよくいただきます。1.1.1でも詳しく解説しているとおり、Web開発はまったくの初心者がちょっと頑張っただけで簡単に学べるようなものではありません。当初、本チュートリアルはある程度のプログラミング経験とWeb開発経験がある読者を対象にしていましたが、現在は開発初心者を含めたより多くの読者を対象とするように書き直しました。現在のRails チュートリアル第3版では、Railsを学ぶための敷居を下げるための重要なステップをいくつか追加してあります (コラム1.1).

コラム1.1 初心者が学びやすくするために

Ruby on Railsチュートリアル第3版では、Railsを学びやすくするためにさまざまな工夫をこらしました。

  • インストールや設定などに関する多くのわずらわしい問題を回避するため、クラウド開発を標準で採用しました (1.2)。
  • Railsを「デフォルトスタック」のまま無改造で使うことにしました。これに伴い、テスティングフレームワークも従来使用していたRSpecから、標準のminitestに切り替えました。
  • 旧版チュートリアルで依存していた多くの外部ライブラリ (RSpec、Cucumber、Capybara、Factory Girl) を廃止しました。
  • テスティングをなるべく簡便かつ柔軟性の高い方法に代えました。
  • Spork、RubyTestの面倒な設定は、なるべく後回しにするか廃止しました。
  • Railsの特定のバージョンでしか使えない機能の記述を減らし、その分、Web開発全般で通用する原理原則についての記述を増やしました。

大改訂を行ったRuby on Railsチュートリアル第3版が、以前の版よりもさらに多くの読者にとって役立つことを願っています。

第1章では、最初に必要なソフトウェアをインストールし、開発環境 (1.2) を整えてRuby on Railsを動かす準備をします。次にhello_appというRailsアプリの最初のサンプル作成に取りかかります。Railsチュートリアルでは、ソフトウェア開発の現場で即座に通用するベストプラクティスを学ぶために、新しいRailsプロジェクトを作成した直後にGit (1.4) を使ったバージョン管理を行います。第1章の最後には、作成したアプリを早々に本番 (production) 環境 (1.5) にデプロイして一般公開するところまで実践します。

第2章では、Railsアプリケーションの基本的な仕組みを体験するために、別のプロジェクトを作成します。このおもちゃアプリ (toy_app) では、scaffold (コラム1.2) を使ってコードを短時間で自動生成します。ただし、scaffoldで自動生成したコードは非常に読みづらいので、第2章では自動生成されたコードについては解説しません。その代わり、生成されたURI (いわゆる URL2) をWebブラウザで確かめるだけにします。

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

コラム1.2 お手軽すぎるScaffoldの甘い誘惑

Railsの作者David Heinemer Hansson氏による有名な動画「15分で作るブログ (英語)」が強い印象を与えたおかげで、Railsは立ち上げ当初から一気に盛り上がりました。この後にも続々同じような動画が作られていますが、いずれもRailsの能力の一端を垣間見るにはうってつけなので、ぜひ一度ご覧ください。ただし、動画では「15分でブログを作る」ためにScaffoldというお手軽生成機能を使っています。Railsの魔法のようなgenerate scaffoldコマンドで自動生成したコードがあるからこそ、このような早業が可能なのです。

実際、筆者はRuby on Rails のチュートリアルを書きながら、あまりにもお手軽にコードを生成できる (訳注: 原文の「quicker, easier, more seductive」は、スターウォーズ・エピソードVのヨーダの台詞の引用) scaffoldの機能を使う誘惑にかられることが何度もありました。しかし、自動生成されたコードは無駄に量が多く複雑で、Rails初心者には向いていません。たとえ運よく動いたとしても、正常に動いている理由を解明するのはおそらく無理です。scaffoldの自動生成コードに頼っている限り、コード自動生成の達人にはなれるかもしれませんが、Railsに関する実践的な知識はほとんど身に付きません。

Ruby on Railsチュートリアルでは、より実践的な知識を身につけるために、Scaffoldとほぼ逆のアプローチで開発を進めていきます。具体的には、第2章で作成する簡単なデモアプリではscaffoldを使いますが、このチュートリアルの中核である第3章以降のサンプルアプリケーションからは、scaffoldを一切使わずに開発を進めていきます。scaffold を使わない代わりに、開発の各ステップで、手頃な一口サイズのコードを書いてもらいます。この一口サイズのコードは、無理なく理解できる程度にシンプルで、かつ、ある程度の歯ごたえとやりがいを得られるように配慮してあります。各ステップで理解する必要のあるコードの量はわずかですが、こうした理解を積み重ねていくことで、最終的にRailsの知識を高いレベルで身につけられるように構成されています。このようにして得た深い知識は柔軟性が高く、どのようなWebアプリを作成する時にも応用が効きます。

1.1はじめに

Ruby on Rails (単に「Rails」と呼ぶこともあります) は、Rubyプログラミング言語で記述された、Web開発フレームワークです。Ruby on Railsは2004年にデビューして以来、急速に成長していきました。現在では、動的なWebアプリケーションを開発するフレームワークとして、最も有力かつ人気のあるフレームワークの1つになっています。AirbnbBasecampDisneyGitHub, HuluKickstarterShopifyTwitterYellow Pagesなど多くの企業でRailsが採用されています。他にも、ENTPthoughtbotPivotal LabsHashrocketHappyFunCorpといった、Railsを専門的に扱う会社も数多くあります。また、Railsを専門にしたフリーランスのコンサルタントやインストラクター、開発者については数えきれません。

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

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

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

1.1.1 前提知識

本チュートリアルで学ぶうえで必要となる前提知識は、「公式には」ありません。Railsチュートリアルには必要な要素がすべて盛り込まれています。中心となるのはもちろんRailsですが、他にもRuby言語やminitest (Railsのテスティングフレームワーク) 、HTMLCSS、若干のJavaScriptSQLのチュートリアルもあります。これまでにも、驚くほど多くの初心者がRuby on Railsチュートリアルで学んできた実績があります。Web開発経験の少ない方も、まずは挑戦してみるのがよいと思います。学習中に知識不足を実感する箇所が出てきたら、この後でご紹介する資料でいつでも学び直してから先に進めばよいのです。「チュートリアルを2回通して行う」という方法もお勧めです。続けてもう一度やってみると、1回目のときよりもずっと知識が身に付いたことを実感できますし、2度目にはもっと短い時間で終えられるでしょう。

「先にRubyを勉強してからRailsを学ぶ方が良いでしょうか?」という質問をよく受けます。この質問への回答ですが、読者の学習スタイルやプログラミング経験次第で異なるため、一口には言えません。Web開発を最初から体系的に学びたい方や、プログラミングの経験が全くない方は、やはりRubyを最初に学んでおくのがよいでしょう。Peter Cooperの『Beginning Ruby』がお勧めです。その一方で、これからRailsで開発を始めようとする人は「とにかくWebアプリケーションを作りたい!」と考えていることがほとんどなので、たった1つのWebページを作成するために分厚いRuby本を一気読みする気にはなれないでしょう。Rubyの概要を急いで学びたいのであれば、Rubyを対話的に学習できるWebサービスTry Ruby3をやってから本チュートリアルを学ぶことをお勧めします。本チュートリアルが自分にはまだまだ難しいと思う方には、Learn Ruby on Rails (Daniel Kehoe) やOne Month Railsを先に学んでおくとよいでしょう。どちらもまったくの初心者を対象としており、本チュートリアルよりも敷居が低く設定されています。

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

  • Code School: プログラミングを対話的に学習できるコース
  • Turing School of Software & Design: コロラド州デンバーで開催されているフルタイム27週間のトレーニングプログラムです。本チュートリアルの読者であれば、RAILSTUTORIAL500のコードを使用して500ドルの割引サービスを受けられます。
  • Bloc: 体系化されたカリキュラムを使った、オンライン上のブートキャンプで、参加者に応じて適切なメンターを配置したり、プロジェクトを通した学習に特化している点が特長です。RAILSTUTORIAL500というクーポンコードを使うと、入会費を500ドル割引できます。
  • Tealeaf Academy: オンラインで受講できるRails開発ブートキャンプです。高度な資料も充実しています。
  • Thinkful: プロのエンジニアと組んでプロジェクトベースで授業を進めるオンラインクラスです。
  • Pragmatic Studio: Mike ClarkとNicole Clarkが教鞭を執っているオンラインのRailsクラスです。2006年に筆者が受講したRailsのコースでは、Mikeの他にProgramming Rubyの著者Dave Thomasも講師を務めていました
  • RailsCasts (Ryan Bates) : 極めて質の高い (ほぼ) 無料のスクリーンキャストです。
  • RailsApps: さまざまなRailsプロジェクトやチュートリアルがトピック別に詳しく特集されています。
  • Railsガイド: トピック毎に分類された最新のRailsリファレンスです (訳注: RailsGuidesの日本語版を「Railsガイド」と呼んでいます。)

1.1.2 本チュートリアルの記法について

本チュートリアルで使用している取り決めや表記は、説明不要なものがほとんどです。ここでは、説明が必要と思われるものについてのみ補足します。

本チュートリアルでは、コマンドライン (ターミナル) のコマンド例が多用されています。簡素化のため、次のようなUnixスタイルのプロンプト (行の頭に「$」を表示するスタイル) を使用して、その例がコマンドラインであることを示しています。

$ echo "hello, world"
hello, world

1.2でも述べているように、本チュートリアルではUnixコマンドラインを最初から利用できるクラウド開発環境の利用 (1.2.1) を、すべてのOSユーザー (特にWindows) の方におすすめしています。Railsにはコマンドラインから実行するコマンドが多数あるので、最初からコマンドラインが利用できるクラウドは非常に便利です。たとえば、1.3.2では以下のrails serverコマンドでローカルdevelopment Webサーバーを実行しています。

$ rails server

Railsチュートリアルにおけるディレクトリの区切りは、コマンドラインのプロンプトと同様にUnixスタイルのスラッシュ「/」を使っています。たとえば、サンプルアプリケーションproduction.rbの設定ファイルは以下のように表します。

config/environments/production.rb

このようなファイルパスは、アプリケーションのルートディレクトリからの相対パスであると理解してください。ルートディレクトリの位置はシステムによって異なり、このクラウドIDE (1.2.1) の場合は以下のようになります。

/home/ubuntu/workspace/sample_app/

この場合、production.rbへの絶対パスは以下のようになります。

/home/ubuntu/workspace/sample_app/config/environments/production.rb

表記を簡単にするため、本チュートリアルでは原則としてconfig/environments/production.rbと記述し、ルートディレクトリより上のパスは省略します。

Railsチュートリアルでは、様々なプログラムの出力結果 (シェルコマンド、バージョン管理ツール、Rubyプログラムの出力結果など) をこまめに記載するようにしています。出力結果は、コンピュータシステムによって微妙に異なるので、本チュートリアルの出力結果と実際の出力が正確に一致するとは限りません。しかし、こうした細かい出力結果の差異が問題になることはないので、それほど気にする必要はありません。コンピュータシステムによっては、コマンドを実行した時にエラーが発生するかもしれません。しかし本チュートリアルでは、あらゆる場面を想定してエラー対策をもれなく記載するような不毛な作業は行っていません。そのような場合は、即座にエラーメッセージをGoogleで検索してください。エラーメッセージをGoogleで検索することは、実際のソフトウェア開発でも使われている基本的なテクニックなので、よい練習にもなります。本チュートリアルのチュートリアルを進めていくうちに何か問題が生じたら、Railsチュートリアルのヘルプ4に記載されているリソースをご覧ください。

RailsチュートリアルではRailsアプリケーションのテスティングも扱っているので、コードでどんなことをするとテストスイートが失敗 (赤色で表示)し、どうするとテストスイートがパス (緑色で表示) するかを実地で学べるようになっています。本チュートリアルでは読みやすさのため、テストが失敗するコードをRED、テストがパスするコードをGREENで表記します。

チュートリアルの各章の最後には演習問題を配置しました。必須ではありませんが、挑戦をおすすめします。本編と演習問題を分けるために、その後のコードリストには原則として解答を付けていません。演習の解答が少ない分、本編で解答を示すようにしています。

最後に、Ruby on Railsチュートリアルで使用されている多くのサンプルコードをわかりやすく記述するために、2つの工夫を加えました。1つは、コードの重要な部分にハイライトを追加したことです。

class User < ActiveRecord::Base
  validates :name,  presence: true
  validates :email, presence: true
end

ハイライト行は通常、コードに追加された行を示しますが、その前に示したコードとの違いを強調していることもよくあります (常にというわけではありませんが)。2つ目は、長いコードの途中を次のように垂直連続ドットで省略したことです。

class User < ActiveRecord::Base
  .
  .
  .
  has_secure_password
end

連続ドットは省略を表しているので、他のコードと一緒にコピーしないようご注意ください

1.2 さっそく動かす

Rubyをインストールし、Railsなどのサポートソフトウェアを一からすべてインストールする練習は、たとえベテランRails開発者にとっても、退屈な作業になることでしょう。OSの違い、バージョンの違い、テキストエディタの設定の違い、IDEの違いなど、環境にばらつきがあると、さまざまな問題が複合して発生してしまいます。自分のPC環境に既に開発環境を持っている方は、それをそのまま利用することをおすすめします。しかしコラム1.1で述べたように、インストールや設定にまつわる問題を回避するためにも、初心者の方は極力クラウド統合開発環境を利用することをおすすめします。クラウドIDEは普通のWebブラウザの中で実行できるため、プラットフォームが異なっても同じように扱えます。この特長は、(Windowsのような) Rails開発環境の構築が面倒なOSでは特に便利です。多少苦労しても構わないのでローカルPC環境でRuby on Railsチュートリアルを学習したいとお考えの方には、InstallRails.com5に従って環境を構築することをお勧めします。(訳注: 日本語では@niwatakoさんの記事「rbenvつかってrailsチュートリアルやってみよう!」がよくまとまっています。ローカルでの環境構築にチャレンジしてみたい方は参考にしてみてください。)

1.2.1 開発環境

開発環境は、Railsプログラマ一人ひとりすべて異なります。開発者は慣れてくるに従い、自分の環境を徹底的にカスタマイズするものだからです。開発環境を大別すると、テキストエディタやコマンドラインを使う環境と、IDE (統合開発環境) の2つに分けられます。そしてRuby on Railsチュートリアルでは、複雑さを避けるためにCloud9という素晴らしいクラウドIDEサービスを使って進めていきます。特に今回の第3版では、Cloud9と提携して読者の皆様にチュートリアル用に最適化された開発環境を提供できたことを心から嬉しく思っています。このようにして構築したRailsチュートリアル用Cloud9ワークスペースには、Ruby、RubyGems、Gitなど、プロ級のRails開発環境に必要なソフトウェアがすべて組み込まれています。Railsだけはインストールされていませんが、これはもちろんチュートリアルのために意図的にそうしてあります (1.2.2)。このクラウドIDEには他にも、Web開発に必要な三種の神器であるテキストエディタ、ファイルブラウザ、コマンドラインターミナル (1.1) もしっかり組み込んであります。また、クラウドIDEのテキストエディタでは、Ruby on Railsの大きなプロジェクトには不可欠とも言うべきグローバルファイル検索も利用できます6。最後にこのクラウドIDEですが、たとえ今後使うことがないとしても (筆者としても、他のエディタの使い方もぜひ知っておく必要があると考えています)、テキストエディタなどの開発ツールで一般にどんなことができるのかを知っておくには最適です。

images/figures/ide_anatomy
図1.1: クラウドIDEの外観

クラウド開発環境を利用するための手順は次のとおりです。

  1. Cloud97でユーザー登録 (無料) を行います。
    (注意: Cloud9を悪用するケースが増えたため、2016年7月よりクレジットカードの入力が必須になりました。後述の手順でRailsチュートリアル専用のワークスペースを作成すれば課金されることはありませんが、Freeプランから別のプランにアップグレードすると課金されるようになるのでご注意ください)
  2. [Go to your Dashboard] をクリックします。
  3. [Create New Workspace] を選択します。
  4. 1.2に示したように、「rails-tutorial」(「rails_tutorial」にしないようご注意ください) というワークスペース名を入力し、[Private to the people I invite] を選択し、Railsチュートリアルのアイコン (Ruby on Railsのアイコンではありません) を設定します。
  5. [Create] をクリックします。
  6. Cloud9でのワークスペースの準備が完了したら、ワークスペースを選択して [Start editing] をクリックします。

Rubyの世界では、インデントに2つのスペースを使用するのがほぼ常識になっているので、このエディタのインデント設定もデフォルトの4から2に変えておくことをおすすめします。インデント設定を変更するには、右上の歯車アイコンをクリックして [Code Editor (Ace)] を選択し、[Soft Tabs] 設定を開いて編集します (1.3)。設定の変更はその場で反映されるので、[Save] ボタンをクリックする必要はありません。

images/figures/cloud9_new_workspace
図 1.2: Cloud9に新しいワークスペースを作成する。
images/figures/cloud9_two_spaces
図 1.3: Cloud9でインデントをスペース2つに設定する。

1.2.2 Railsをインストールする

1.2.1の開発環境には、必要なソフトウェアがすべて含まれていますが、Railsだけは含まれていません8。Railsをインストールするには、gemというコマンドを使用します。このコマンドは、RubyGemsというパッケージマネージャによって提供されているもので、リスト1.1のコマンドをコマンドラインターミナルに入力するときに使用します。なお、ローカルシステム上で開発する場合は普通のターミナルを使用します。クラウドIDEを使用している場合は、1.1のコマンドラインエリアに入力します。

リスト1.1: バージョンを指定してRailsをインストールする。
$ gem install rails -v 4.2.2

コマンドの後ろにある-vフラグによって、インストールするRailsのバージョンを指定できます。本チュートリアルではすべてこのバージョンを前提としていますので、バージョン番号を変えるとチュートリアルと異なる結果になる可能性があります。

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

コンピュータープログラミングにおける古来の伝統に従い、最初に作るアプリケーションは「hello, world」を表示するプログラムにしましょう。具体的には、Webページに「Hello, world」という文字列を表示するだけの単純なアプリケーションを、開発環境 (1.3.4) と本番環境 (1.5) でそれぞれ作成します。

どんなRailsアプリケーションも最初の作成手順は基本的に同じです。rails newコマンドを実行して作成します。このコマンドを実行するだけで、指定のディレクトリにRailsアプリケーションのスケルトンを簡単に作成できます。1.2.1で推奨しているCloud9 IDEを利用しない場合は、少し追加の手順が必要です。Railsプロジェクトで使用するためのworkspaceディレクトリをUnixのmkdirコマンドで作成し (リスト 1.2)、次にcdコマンドでそのディレクトリに移動してください (リスト1.2)。Unixコマンドに慣れていない方はコラム1.3も参照してください。

リスト1.2: Railsプロジェクトで使用するworkspaceディレクトリを作成する(クラウドの場合は不要)。
$ cd                  # ホームディレクトリに移動
$ mkdir workspace     # workspaceディレクトリを作成
$ cd workspace/       # 作成したworkspaceディレクトリに移動
コラム 1.3 急いで学びたい人のためのUnixコマンドライン講座

WindowsユーザーやMac OS Xユーザーの多くはコマンドラインというものに馴染みがないことでしょう (Mac OS Xユーザーの方がほんのわずかコマンドラインを知っている人は多いかもしれませんが)。幸い、今はおすすめのクラウド開発環境のおかげでUnixコマンドラインをみな同じように扱うことができ、Bashなどの標準的なシェルコマンドラインインターフェイスを実行できます (訳注: 「シェル」とは、実際に動くコマンドやプログラムに文字どおり「かぶさっている」インターフェイスと考えるとよいでしょう)。

コマンドラインの基本的な仕組みは本当にシンプルです。ユーザーはコマンドを発行 (issue) することで、実にさまざまな操作を実行できます。ディレクトリの作成ならmkdirコマンド、ファイルの移動やリネームはmvコマンド、ファイルのコピーならcpコマンド、ファイルシステム内でのディレクトリの移動はcdコマンド、という具合です。GUI (グラフィカルユーザーインターフェイス) しか使ったことのないユーザーからすると、コマンドラインの黒い画面は何やら恐ろしげでとっつきが悪いように見えるかもしれませんが、見た目ほど当てにならないものはありません。コマンドラインはそれ自体が強力なツールであり、エンジニアにとってなくてはならない道具箱なのです (訳注: 操作を誤ったときの被害もその分甚大ですが)。そうでなければ、どうしてエンジニアが揃いも揃ってコマンドラインを使うでしょうか。ベテラン開発者のデスクトップ画面を覗きこめば、十中八九どころか99%は、黒いターミナルウィンドウがいくつも開き、そこで多数のコマンドラインシェルが忙しく実行されているはずです。

コマンドラインについて話しだすときりがないので深入りはしませんが、本チュートリアルで必要なUnixコマンドラインのコマンドはほんのわずかしかありませんのでご安心ください (1.1)。Unixのコマンドラインについてもっとお知りになりたい方は、Mark Bates著『Conquering the Command Line』をご覧ください。無料のオンライン版電子書籍版、スクリーンキャストもあります。

説明 コマンド
ディレクトリ内容の表示 ls $ ls -l
ディレクトリの作成 mkdir <ディレクトリ名> $ mkdir workspace
ディレクトリの移動 cd <ディレクトリ名> $ cd workspace/
上のディレクトリに移動 $ cd ..
ホームディレクトリに移動 $ cd ~ ($ cdだけでもよい)
ホームディレクトリの下のパスに移動 $ cd ~/workspace/
ファイルの移動やリネーム mv <移動元/現在の名前> <移動先/変更後の名前> $ mv README.rdoc README.md
ファイルのコピー cp <コピー元> <コピー先> $ cp README.rdoc README.md
ファイルの削除 rm <ファイル名> $ rm README.rdoc
空のディレクトリの削除 rmdir <ディレクトリ名> $ rmdir workspace/
中身のあるディレクトリの削除 rm -rf <ディレクトリ名> $ rm -rf tmp/
ファイルの内容の結合または表示 cat <ファイル名> $ cat ~/.ssh/id_rsa.pub
表1.1: 主要なUnixコマンド。

ローカルシステムまたはクラウドIDEで行う次の手順は、リスト1.3のコマンドを使用した最初のアプリケーションの作成です。リスト1.3のコマンドでは、Railsのバージョンを(_4.2.2_)と明示的に指定している点にご注目ください。このようにバージョンを指定することで、リスト1.1と同じバージョンのRailsで、最初のアプリケーションと同じファイル構造を作成することができます。(リスト1.3を実行すると「Could not find ’railties'」というエラーが表示される場合は、インストールしたRailsのバージョンが正しくない可能性があります。リスト1.1のとおりにコマンドを実行したかどうかを念のためダブルチェックしてください。

リスト1.3: rails newを実行する (バージョン番号を指定)。
$ cd ~/workspace
$ rails _4.2.2_ new hello_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
Fetching gem metadata from https://rubygems.org/..........
Fetching additional metadata from https://rubygems.org/..
Resolving dependencies...
Using rake 10.3.2
Using i18n 0.6.11
.
.
.
Your bundle is complete!
Use `bundle show [gemname]` to see where a bundled gem is installed.
         run  bundle exec spring binstub --all
* bin/rake: spring inserted
* bin/rails: spring inserted

リスト1.3の下の方にご注目ください。rails newを実行すると、ファイルの作成後にbundle installコマンドが自動的に実行されています。このbundle installコマンドの意味については1.3.1の最初で詳しくご説明します。

ご覧のとおり、railsコマンドを実行すると大量のファイルとディレクトリが作成されます。Webアプリケーションのディレクトリをどう構成するかは本来自由なのですが、RailsのようなWebフレームワークではディレクトリとファイルの構造 (図1.4) はこのように標準化されています。そのおかげで、ディレクトリ構成に悩むことなく、実際に動作するアプリケーションを即座にゼロから作成できるのです。ファイル/ディレクトリ構造がすべてのRailsアプリで標準化されているおかげで、他の開発者の書いたRailsのコードが読みやすくなります。これはWebフレームワークを導入する大きなメリットです。Railsがデフォルトで作成するファイルについては表1.2を参照してください。これらのファイルやディレクトリの目的については本チュートリアル全体に渡って説明いたします。特に、5.2.1以降ではRails 3.1の新機能であるアセットパイプラインの一部となるapp/assetsディレクトリについて詳しく説明します。アセットパイプラインによって、CSS (Cascading Style Sheet) やJavaScriptファイルなどのアセット (資産) を簡単に編成したりデプロイすることができます。

images/figures/directory_structure_rails_3rd_edition
図1.4 新規作成された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/ アプリケーションのテスト
tmp/ 一時ファイル
vendor/ サードパーティのプラグインやgemなど
vendor/assets サードパーティのプラグインやgemで使用するCSS (Cascading Style Sheet)、JavaScriptファイル、画像などのアセット
README.rdoc アプリケーションの簡単な説明 (訳注: 近年は .rdocよりも .md ファイルの方がよく使われているようです)
Rakefile rakeコマンドで使用可能なタスク
Gemfile このアプリケーションに必要なGemの定義ファイル
Gemfile.lock アプリケーションのすべてのコピーが同じgemのバージョンを使用していることを確認するために使用されるgemのリスト
config.ru Rackミドルウェア用の設定ファイル
.gitignore Gitに取り込みたくないファイルを指定するためのパターン
表1.2: デフォルトのRailsフォルダ構造まとめ。

1.3.1 Bundler

Railsアプリケーションを新規作成したら、次はBundlerを実行して、アプリケーションに必要なgemをインストールおよびインクルードします。1.3でも簡単に説明したように、Bundlerはrailsによって自動的に実行 (この場合はbundle install) されます。ここではデフォルトのアプリケーションgemを変更してBundlerを再度実行してみます。そのために、テキストエディタでGemfileを開きます (クラウドIDEの場合は、ファイルナビゲーターで矢印をクリックしてサンプルアプリのディレクトリを開き、Gemfileアイコンをダブルクリックします)。Gemfileの内容は、だいたい1.5リスト1.4のようになります。バージョン番号など細かな点で多少の違いがあるかもしれません。Gemfileの内容はRubyのコードですが、今は文法を気にする必要はありません。Rubyの詳細については第4章で説明します。ファイルやディレクトリが1.5のように表示されない場合、ナビゲーターの歯車アイコンをクリックして [Refresh File Tree] を選択します (一般に、ファイルやディレクトリがうまく表示されていない場合はこのようにファイルツリーを再表示してみてください)。

images/figures/cloud9_gemfile
図1.5: デフォルトのGemfileをテキストエディタで開く。
リスト1.4: hello_appディレクトリにあるデフォルトのGemfile
source 'https://rubygems.org'


# Bundle edge Rails instead: gem 'rails', github: 'rails/rails'
gem 'rails', '4.2.2'
# Use sqlite3 as the database for Active Record
gem 'sqlite3'
# Use SCSS for stylesheets
gem 'sass-rails', '~> 5.0'
# 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.0'
# 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', '~> 2.0'
# bundle exec rake doc:rails generates the API under doc/api.
gem 'sdoc', '~> 0.4.0', group: :doc

# Use ActiveModel has_secure_password
# gem 'bcrypt', '~> 3.1.7'

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

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

group :development, :test do
  # Call 'debugger' anywhere in the code to stop execution and get a
  # debugger console
  gem 'byebug'

  # Access an IRB console on exceptions page and /console in development
  gem 'web-console', '~> 2.0.0.beta2'

  # Spring speeds up development by keeping your application running in the
  # background. Read more: https://github.com/rails/spring
  gem 'spring'
end

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

gemコマンドで特定のバージョン番号を指定しない限り、Bundlerは自動的に最新バージョンのgemを取得してインストールします。たとえば、Gemfileに以下のような記述があるとします。

gem 'sqlite3'

このsqlite3というgemのバージョンを指定する主な方法は2とおりあります。これにより、Railsで使用されるgemのバージョンを「ある程度」制御できます。1番目の方法は次のとおりです。

gem 'uglifier', '>= 1.3.0'

uglifierのバージョンが1.3.0以上であれば最新バージョンのgemがインストールされます。極端に言えば、バージョンが7.2であってもそれが最新ならインストールされます (なお、uglifierはAsset Pipelineでファイル圧縮を行うためのものです)。2番目の方法は次のとおりです。

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

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

リスト1.4Gemfileを、実際に使用する正確なバージョンのgemに置き換えたものをリスト1.5に示します。なお、この置換えのついでに、sqlite3 gemをdevelopment環境とtest環境 (7.1.1) だけで使用する (つまりproduction環境では使用しない) ように変更している点にもご注目ください。これは、後でHerokuで使用するデータベースと競合する可能性を防ぐための処置です (1.5)。

リスト1.5: Ruby gemごとにバージョンを明示的に指定したGemfile
source 'https://rubygems.org'

gem 'rails',                '4.2.2'
gem 'sass-rails',           '5.0.2'
gem 'uglifier',             '2.5.3'
gem 'coffee-rails',         '4.1.0'
gem 'jquery-rails',         '4.0.3'
gem 'turbolinks',           '2.3.0'
gem 'jbuilder',             '2.2.3'
gem 'sdoc',                 '0.4.0', group: :doc

group :development, :test do
  gem 'sqlite3',     '1.3.9'
  gem 'byebug',      '3.4.0'
  gem 'web-console', '2.0.0.beta3'
  gem 'spring',      '1.1.3'
end

アプリケーションのGemfileの内容をリスト1.5で置き換えたら、bundle installを実行してgemをインストールします9

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

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

1.3.2 rails server

1.3rails newコマンドと1.3.1bundle installコマンドを実行したことにより、実際に動かすことのできるアプリケーションが作成されました。ありがたいことに、Railsには開発マシンでのみブラウズできるローカルWebサーバーを起動するためのコマンドラインプログラム (スクリプト) が付属しているので、以下のコマンドを実行するだけでRailsアプリケーションを簡単に起動することができます。現在の環境に応じてアプリケーションを起動する正確なコマンドは次のとおりです。ローカルシステムの場合は、rails serverを実行するだけで済みます (リスト1.6)。Cloud9の場合は、IPバインディングアドレスポート番号も指定する必要があります。(リスト1.7)10。これらの値は、クラウドの外からRailsサーバーにアクセスするために使用されます (Cloud9 では特殊な環境変数$IP」と「$PORT」を使用してIPアドレスとポート番号を動的に割り当てます。コマンドラインでecho $IPまたはecho $PORTと入力すると値を確認できます)。JavaScriptランタイムがインストールされていないというエラーが表示された場合は、GitHubのexecjsページにあるインストール可能なランタイムの一覧からJavaScriptランタイムを入手してください。個人的にはNode.jsがおすすめです。

リスト1.6: ローカルコンピュータでRailsサーバーを実行する。
$ cd ~/workspace/hello_app/
$ rails server
=> Booting WEBrick
=> Rails application starting on http://localhost:3000
=> Run `rails server -h` for more startup options
=> Ctrl-C to shutdown server
リスト1.7: クラウドIDEでRailsサーバーを実行する。
$ cd ~/workspace/hello_app/
$ rails server -b $IP -p $PORT
=> Booting WEBrick
=> Rails application starting on http://0.0.0.0:8080
=> Run `rails server -h` for more startup options
=> Ctrl-C to shutdown server

どちらのオプションを使用する場合であっても、rails serverコマンドの実行は別のターミナルタブで行うことをおすすめします。こうしておけば、rails serverコマンドに邪魔されずに、最初のターミナルタブで他のコマンドを実行できるからです1.61.7。既に最初のタブでサーバーを開始している場合は、Ctrl+Cを押すとサーバーを終了できます11。Railsアプリケーションを表示するには、ローカルサーバーの場合はhttp://localhost:3000/をブラウザで開きます。クラウドIDEの場合は、[Share] を開いて、開きたいアプリケーションのアドレスをクリックします (1.8)。どちらの場合も、1.9のようにブラウザにRailsアプリケーションが表示されます。

images/figures/new_terminal_tab
図1.6: 新しいターミナルタブを開く。
images/figures/rails_server_new_tab
図1.7: 別のタブでRailsサーバーを実行する。
images/figures/share_workspace
図1.8: クラウドワークスペース上で実行しているローカルサーバーを共有する。
images/figures/riding_rails_3rd_edition
図1.9: rails serverを実行したときのデフォルトのRailsページ。

最初のアプリケーションに関する情報を表示するには、画面の [About your application’s environment] リンクをクリックします。1.10のように詳細な情報が表示されます (バージョン番号などは異なることもあります)。もちろん、いずれデフォルトのRailsページは不要になりますが、アプリケーションが動いているのを見るのは気分のいいものです。1.3.4ではこのデフォルトページを削除し、カスタマイズしたホームページに置き換える予定です。

images/figures/riding_rails_environment_3rd_edition
図1.10: アプリケーション環境が表示されているデフォルトページ。

1.3.3 Model-View-Controller (MVC)

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

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

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

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

1.3.4 Hello, world!

記念すべき最初のMVCフレームワークアプリケーションとして、先ほど作ったアプリにほんのちょっぴり変更を加えることにしましょう。「Hello World」という文字列を表示するだけのコントローラのアクションを追加します (コントローラのアクションについては2.2.2で詳しく解説します)。この改造が終わると、デフォルトのRailsページは1.9のように「hello, world」ページが表示されます。これがこのセクションでの目標です。

名前から想像されるように、コントローラのアクションはコントローラ内で定義します。ここでは、Applicationという名前のコントローラの中にhelloという名前のアクションを作成することにします。実際、この時点ではコントローラはApplicationひとつしかありません。次のコマンドを実行すると、現在あるコントローラを確認できます。

$ ls app/controllers/*_controller.rb

新しいコントローラの作成は2で行います。リスト1.8に、helloを定義したところを示します。ここではrender関数で「hello, world!」というテキストを表示しています。この時点ではRubyの文法については気にする必要はありません。4で詳しく解説します。

リスト1.8: Applicationコントローラにhelloを追加する。app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
  # Prevent CSRF attacks by raising an exception.
  # For APIs, you may want to use :null_session instead.
  protect_from_forgery with: :exception

  def hello
    render text: "hello, world!"
  end
end

表示したい文字列を返すアクションを定義したので、今度はデフォルトのページ (1.10) の代わりにこのアクションを使用するようRailsに指示します。そのためには、Railsのルーター (router) を編集します。ルーターはコントローラとブラウザの間に配置され (1.11)、ブラウザからのリクエストをコントローラに振り分ける (=ルーティング) 役割を果たします (1.11では簡単のためルーターは省略していますが、2.2.2で詳しく解説します)。ここではデフォルトのページを差し替えたいので、ルートのルーティング (ルート URLにアクセスした場合のルーティング) を変更することにします。たとえばhttp://www.example.com/というURLの末尾は「/」になっているので、ルートURLは単に「/」(スラッシュ) と簡略表記することもあります (訳注: 本チュートリアルではrouteやroutingを「ルーティング」、rootを「ルート」と表記します)。

リスト1.9に示したように、Railsのルーティングファイル (config/routes.rb) には、ルートルーティングの構成方法がコメント行に示されています。「welcome」はコントローラ名、「index」はコントローラ内のアクションです。ルートルーティングを有効にするには、「#」文字を削除してコメントを解除し、コードを書き換えます (リスト1.10)。これにより、RailsのルートルーティングにアクセスするとApplicationコントローラのhelloアクションが動作します (1.1.2でも説明したとおり、途中のドットだけの行は省略を意味しているので、その部分はそのままコピーしないでください)。

リスト1.9: デフォルトのルートルーティング (コメントアウトされた状態) config/routes.rb
Rails.application.routes.draw do
  .
  .
  .
  # You can have the root of your site routed with "root"
  # root 'welcome#index'
  .
  .
  .
end
リスト1.10: ルートルーティングを設定する。config/routes.rb
Rails.application.routes.draw do
  .
  .
  .
  # You can have the root of your site routed with "root"
  root 'application#hello'
  .
  .
  .
end

リスト1.8のコードとリスト1.10のコードを使用すると、期待どおりルートルーティングから「hello, world!」が返されるようになります (1.12)。

images/figures/hello_world_hello_app
図1.12: 「hello, world!」をブラウザで表示する。

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

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

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

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

推奨環境であるクラウドIDE (1.2.1) にはデフォルトでGitが導入されていますので、追加で導入する必要はありません。その他の場合は、InstallRails.com (1.2) の指示に従ってGitをシステムに導入してください。

初めてのシステムセットアップ

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

$ git config --global user.name "Your Name"
$ git config --global user.email your.email@example.com
$ git config --global push.default matching
$ git config --global alias.co checkout

このGit configurationで設定する名前やメールアドレスは、今後リポジトリ上で一般に公開されますのでご注意ください (最初の2行の設定以外は必須ではありません。3行目は、今後のGitリリースでの前方互換性のために使用されるオプション設定です。4行目は、checkoutコマンドをもっと短いcoと入力できるようにするためのオプション設定です。coコマンドエイリアスを設定していなくても動作するように、本チュートリアルではcheckoutコマンドを使用するようにしていますが、著者は普段からgit coだけを常に使っています)

初めてのリポジトリセットアップ

今度は、リポジトリ (repoと略されることもあります) ごとに作成の必要な作業を行います。まず、Railsアプリケーションのルートディレクトリに移動し、新しいリポジトリの初期化を行います。

$ git init
Initialized empty Git repository in /home/ubuntu/workspace/hello_app/.git/

次にgit add -Aを実行し、プロジェクトのファイルをリポジトリに追加します。

$ git add -A

このコマンドを実行すると、現在のディレクトリにあるファイルがすべて追加されます。ただし、.gitignoreに記載されているパターンにファイル名がマッチする場合、そのファイルは追加されません。.gitignoreファイルは、rails newコマンドを実行すると自動的に生成され、Railsプロジェクト用のパターンも記入されます。もちろん、自分でパターンを追加してもかまいません12

Gitにプロジェクトのファイルを追加すると、最初はステージングエリアという一種の待機用リポジトリに置かれ、コミットを待ちます。安全のため、いきなりコミットしないようになっているのです。ステージングエリアの状態を知るにはstatusコマンドを使用します。

$ git status
On branch master

Initial commit

Changes to be committed:
  (use "git rm --cached <file>..." to unstage)

  new file:   .gitignore
  new file:   Gemfile
  new file:   Gemfile.lock
  new file:   README.rdoc
  new file:   Rakefile
  .
  .
  .

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

ステージングエリアで控えている変更を本格的にリポジトリに反映 (コミット) するには、commitコマンドを使います。

$ git commit -m "Initialize repository"
[master (root-commit) df0a62f] Initialize repository
.
.
.

-mフラグを使用すると、コミットメッセージをコマンドラインで直接指定できます。-mフラグを使用しない場合はシステムのデフォルトのエディタが開き、そこでコミットメッセージを入力します (本チュートリアルでは常に-mフラグを使用するようにしています)。

ここでコミットについて少し解説しておきます。Gitにおけるコミットは、あくまでローカルマシン上での操作であることに注意してください。git pushコマンドで変更をリモートリポジトリにプッシュする方法については1.4.4で解説します。

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

$ git log
commit df0a62f3f091e53ffa799309b3e32c27b0b38eb4
Author: Michael Hartl <michael@michaelhartl.com>
Date:   Wed August 20 19:44:43 2014 +0000

    Initialize repository

ログがある程度以上長い場合は、qキーを押して終了します。

1.4.2 Gitのメリット

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

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

ここでは、Unixコマンドのlsapp/controllers/ディレクトリの中身を表示した後、rmコマンドをうっかり実行してこのディレクトリを削除してしまったとします (表1.1)。-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  concerns/

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

1.4.3 Bitbucket

Gitを使用してプロジェクトをバージョン管理下に置くことができたので、今度はBitbucketにソースコードをアップロードしてみましょう。BitbucketはGitリポジトリのホスティングと共有に特化したサイトです (以前のチュートリアルではGitHubをリポジトリとして使用していました。乗り換えた理由についてはコラム1.4をご覧ください)。リポジトリをBitbucketにわざわざプッシュするのには2つの理由があります。1つ目は、ソースコード (とそのすべての変更履歴) の完全なバックアップを作成することです。2つ目は、他の開発者との共同作業をより簡単に行うことです。

コラム1.4 GitHubとBitbucket

GitHubとBitbucketは、現時点でのGitリポジトリの2大人気サイトです。両者のサービスは非常に似通っています。どちらも、Gitリポジトリのホスティングと共同作業を行うことができ、リポジトリの表示や検索を行いやすくしてくれます。本チュートリアルでリポジトリを採用するうえで重要な両者の違いは、GitHubは「リポジトリを一般公開する場合は無料、公開しない場合は有料」なのに対し、Bitbucketは「共同作業者が一定数以下ならリポジトリを公開しなくても無料、共同作業者が一定数を超えると有料」である点です。なお、どちらも容量制限はありません。もちろん読者の皆様は、目的に応じてどちらのサービスを選んでもかまいません。

本チュートリアルの前のエディションでは、オープンソースコードのサポートを強調するGitHubを採用していました。しかしチュートリアルの目的には、Webアプリケーションのリポジトリがデフォルトで非公開になっている方がセキュリティ上好都合です。Webアプリケーションのリポジトリには、暗号化キーやパスワードなどの機密情報が含まれる可能性があります。このような情報を利用されると、サイトのコード実行のセキュリティがおびやかされるかもしれません。もちろん、.gitignoreなどを適切に利用すれば、そうした機密情報を適切に扱うことができます。しかしそのためにはそれなりの経験が必要であり、慣れた開発者でもときに扱いを間違えてしまうことがありえるのです。

本チュートリアルで作成したサンプルWebアプリケーションそれ自体は公開してもまったく問題はありませんが、上の理由により、一般公開されているリポジトリに置くことには若干のリスクが生じます。そういうわけで、リモートリポジトリはデフォルトで非公開であるのが望ましく、なるべく安全側に倒しておきたいと考えます。GitHubはリポジトリを一般公開しない場合は有料ですが、Bitbucketはリポジトリを一般公開しなくても容量無制限かつ無料で利用できます。チュートリアルのためにはGitHubよりもBitbucketの方が好都合であると言えます。

Bitbucketの利用開始は簡単です。

  1. Bitbucketアカウントがない場合はアカウントを作成します
  2. 公開鍵をクリップボードにコピーします。リスト1.11に示したように、クラウドIDEを使用していればcatコマンドで公開鍵を表示できるので、それを選択、コピーします。クラウドIDEを使用しておらず、リスト1.11のコマンドを実行しても何も表示されない場合は、Bitbucketアカウントに公開鍵をインストールする方法 (英語) を参照してください。
  3. Bitbucketに公開鍵を追加するには、右上にあるアバター画像をクリックして [Manage account]、[SSH keys] の順に選択します (1.13)。
リスト1.11: catコマンドで公開鍵を出力する。
$ cat ~/.ssh/id_rsa.pub
images/figures/add_public_key
図1.13: SSH公開鍵を追加する。

公開鍵の追加が終わったら、[Create] をクリックして新しいリポジトリを作成します 1.14)。プロジェクトの情報を入力したら、[This is a private repository] チェックボックスが「オン」になっていることを確認します (オフにしないでください!)。[Create repository] をクリックしてリポジトリを作成したら、[Command line] > [I have an existing project] をクリックしてそこに表示される指示に従います (リスト1.12)。リスト1.12のように表示されない場合は、公開鍵が正しく追加されていない可能性がありますので、公開鍵の追加をやり直すことをおすすめします。リポジトリをプッシュするときに「Are you sure you want to continue connecting (yes/no)?」と表示されたらyesと入力します。

images/figures/create_first_repository_bitbucket
図1.14: Bitbucketに最初のアプリのリポジトリを作成する。
リスト1.12: Bitbucketへのリポジトリ追加とリポジトリへのプッシュ。
$ git remote add origin git@bitbucket.org:<username>/hello_app.git
$ git push -u origin --all # リポジトリとブランチをすべてプッシュする

リスト1.12の最初のコマンドは、BitbucketをリポジトリのoriginとしてGitの設定ファイルに追加するためのものです。次のコマンドでは、ローカルのリポジトリをリモートのoriginにプッシュします (-uフラグについては気にする必要はありません。気になるのであれば "git set upstream"で検索してみてください)。もちろん、<username>は自分の名前に置き換えてください。たとえば、著者が実行したコマンドは以下のとおりです。

$ git remote add origin git@bitbucket.org:mhartl/hello_app.git

上のコマンドを実行すると、hello_appのリポジトリのページがBitbucketに作成されます。このページでは、ファイルの参照、全コミット履歴などさまざまな機能を利用できます (1.15)。

images/figures/bitbucket_repository_page
図1.15: Bitbucketのリポジトリページ。

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

1.4.3の手順に沿って進めた場合、README.rdocファイルが自動的に認識されず、READMEがないというメッセージが表示されることに気付いたでしょう (1.16)。この表示は、rdoc形式がBitbucketでは標準のREADMEとしてサポートされていないために起こります。実のところ、著者も含めほとんどの開発者はMarkdown形式を使用しています。ここではREADME.rdocファイルの名前をREADME.mdに変更し、チュートリアル中にコンテンツを追加できるようにしておきましょう。それと同時に、Gitでbranch、edit、commit、mergeを行う際にお勧めのワークフローの実例をご覧いただきます13

images/figures/bitbucket_no_readme
図1.16: Bitbucketで表示される「READMEが見つからない」メッセージ。

ブランチ (branch)

Gitは、ブランチ (branch) を極めて簡単かつ高速に作成することができます。ブランチは基本的にはリポジトリのコピーで、ブランチ上では元のファイルを触らずに新しいコードを書くなど、自由に変更や実験を試すことができます。通常、親リポジトリはmasterブランチと呼ばれ、トピックブランチ (短期間だけ使う一時的なブランチ) は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.4coエイリアスを設定した場合は、git co -b modify-READMEと入力することもできます)。

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

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

変更 (Edit)

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

$ git mv README.rdoc README.md

続いて、リスト1.13の内容をREADME.mdに入力します。

リスト1.13: 新しいREADMEファイル「README.md」の内容。
# Ruby on Rails Tutorial: "hello, world!"

This is the first application for the
[*Ruby on Rails Tutorial*](http://www.railstutorial.org/)
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

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

  modified:   README.md

この時点で、1.4.1.2のようにgit add -Aを実行することもできますが、git commitには現存するすべてのファイル (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
$ <really screw up the branch>
$ git add -A
$ git commit -a -m "Major screw up"
$ git checkout master
$ git branch -D topic-branch

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

プッシュ (push)

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

$ git push

1.4.4.2で約束したとおり、Markdownで記述したファイルがBitbucket上できれいに整形されました (1.17)。

images/figures/new_readme_bitbucket
図1.17 Markdowndでフォーマットされた改良版READMEファイル

1.5 デプロイする

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

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

著者のお気に入りはHerokuで、Railsを含むRuby Webアプリ用のホスティングプラットフォームです。Herokuは、ソースコードのバージョン管理にGitを使用していれば、Railsアプリケーションを簡単に本番環境にデプロイできます (Gitを導入したのは、まさにこのHerokuで使うためでもあります。まだGitをインストールしていない方は1.4を参照してください)。さらに、Herokuのfree tierプランには、チュートリアルでの利用を含むさまざまな用途のための機能が十分過ぎるほど備わっています。実際、本チュートリアルの最初の2つのエディションもHerokuに無料でホスティングしていたのです。Herokuに置いたチュートリアルは、数百万ものリクエストをこなしながら、1セントも支払う必要はありませんでした。

この章では、最初のアプリケーションをHerokuにデプロイします。作業内容の一部に少しばかり高度な部分も含まれていますが、今はすべてを理解しておく必要はありませんのでご安心ください。今大事なのは、この章の終わりまで手順を進めることで、作成したアプリケーションを実際のWebサービスとしてデプロイすることです。

1.5.1 Herokuのセットアップ

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

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

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

また、sqlite3 gemが本番環境には含まれていない点にも注目してください。これは、今回の本番環境であるHerokuではSQLite3をサポートしていないため、明示的に本番環境からsqlite3 gemを外す必要があります (本番環境に含めてしまうと、デプロイ時にエラーが発生します)。更新したGemfile1.14に示します。

リスト1.14: 追加のgemを含んだGemfile
source 'https://rubygems.org'

gem 'rails',        '4.2.2'
gem 'sass-rails',   '5.0.2'
gem 'uglifier',     '2.5.3'
gem 'coffee-rails', '4.1.0'
gem 'jquery-rails', '4.0.3'
gem 'turbolinks',   '2.3.0'
gem 'jbuilder',     '2.2.3'
gem 'sdoc',         '0.4.0', group: :doc

group :development, :test do
  gem 'sqlite3',     '1.3.9'
  gem 'byebug',      '3.4.0'
  gem 'web-console', '2.0.0.beta3'
  gem 'spring',      '1.1.3'
end

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

bundle installに特殊なフラグ「--without production」を追加すると、本番用のgem (この場合はpgrails_12factor) はローカルの環境にはインストールされません。

$ bundle install --without production

リスト1.14で追加したgemは本番環境でしか使用しないので、このフラグを追加したコマンドを実行すると本番用gemはローカルに追加されません。今このコマンドを実行するのは、後の本番環境へのデプロイに備えてGemfile.lockを更新し、pg gemとrails_12factor gem、Rubyバージョンの指定をGemfile.lockに反映する必要があるためです。以下を実行して変更をコミットできます。

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

次にHerokuのアカウントを新規作成して設定します。最初にHerokuでユーザー登録します。続いて、自分のシステムにHerokuコマンドラインクライアントがインストールされているかどうかを確認します。

$ heroku version

クラウドIDEをお使いの場合は、Herokuのバージョン番号とともにheroku CLIが利用可能であるというメッセージが表示されます。クラウドIDEを使用していない場合は、Heroku Toolbeltをインストールする必要があります18

Herokuのコマンドラインインターフェイス (CLI) がインストールされていることが確認できたら、いよいよherokuコマンドでログインしてSSHキーを追加します。

$ heroku login
$ heroku keys:add

最後にheroku createコマンドを実行して、Herokuサーバーにサンプルアプリケーションの実行場所を作成します (リスト1.15)。

リスト1.15: Herokuに新しいアプリケーションを作成する。
$ heroku create
Creating damp-fortress-5769... done, stack is cedar
http://damp-fortress-5769.herokuapp.com/ | git@heroku.com:damp-fortress-5769.git
Git remote heroku added

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

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

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

$ git push heroku master

(警告メッセージが若干表示されることがありますが、今は無視してください。詳しくは7.5で解説します)。

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

失礼、その2はありません。以上でおしまいです。デプロイされたアプリケーションの表示は、heroku create (リスト1.15) を実行した際に生成されたアドレスをブラウザで開くだけです (もちろんここに表示されている著者のアドレスではなく、あなたのアドレスを使ってください)。クラウドIDEではなくローカルコンピュータで作業している場合は、heroku openコマンドでブラウザ表示することもできます。スタイル追加の結果を図1.18に示します。ページの内容は1.12とまったく同じですが、今やそれがインターネット上の本番Webページとして堂々と公開されているのです。

images/figures/heroku_app_hello_world
図1.18: Heroku上で動いている最初のRailsチュートリアルアプリケーション。

1.5.4Herokuコマンド

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

$ heroku rename rails-tutorial-hello

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

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

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

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

最高ですね。)

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

1.6 最後に

この章ではインストール、開発環境の設定、バージョン管理、本番環境へのデプロイなど、多くの課題を達成しました。次の章では、この1で学んだことを基礎として、データベースを備えたtoyアプリを製作し、Railsでどんなことができるかをさらに詳しく学びます。

ここまでの進捗をTwitterに投稿したりFacebookで誰かに知らせたい場合は、以下のリンクをお使いください。

Railsチュートリアルのメーリングリスト19 (英語) への参加もおすすめします。Ruby on Railsチュートリアルの更新情報をいち早く知ることができるほか、お得なクーポンコードを入手することもできます。(訳注: 日本語の情報は @RailsTutorialJP で紹介しているので、よければフォローしてみてください。誤訳の報告も歓迎です! 他、気になる点などあればお問い合わせフォームからご連絡ください)

1.6.1 本章のまとめ

  • Ruby on Railsとは、Web開発のためのフレームワークであり、Rubyプログラミング言語によって記述されている。
  • 事前設定済みのクラウド環境を利用することで、Railsのインストール、アプリケーションの生成、生成されたファイルの編集を簡単に行うことができる。
  • Railsにはrailsという名前のコマンドラインコマンドがあり、rails newで新しいアプリケーションを生成したり、rails serverでローカルサーバーを実行したりできる。
  • コントローラのアクションを追加したり、ルートルーティングを変更したりするだけで「hello, world」アプリケーションを作成できる。
  • Gitによるバージョン管理を導入し、Bitbucketの非公開リポジトリにプッシュする理由は、データの喪失を防止し、他の開発者との共同作業を行えるようにするため。
  • 作成したアプリケーションをHerokuの本番環境にデプロイした。

1.7 演習

: 『演習の解答マニュアル (英語)』にはRuby on Railsチュートリアルブックのすべての演習の解答が掲載されており、www.railstutorial.orgでチュートリアルを購入いただいた方には無料で付属します。

  1. リスト1.8helloアクションを書き換え、「Hello, world!」の代わりに「hola, mundo!」と表示されるようにしてみましょう。課外作業: Railsの表示では「非ASCII文字」もサポートされています。スペイン語特有の逆さ感嘆符「¡」を含む「¡Hola, mundo!」を表示してみましょう (1.19)20
  2. リスト1.8helloアクションを複製して、第2のアクションgoodbyeを追加しましょう。このアクションは、「goodbye, world!」というテキストを表示します。リスト1.10のルーティングを編集して、ルートルーティングの割り当て先をhelloアクションからgoodbyeアクションに変更します (1.20)。
images/figures/hola_mundo
図1.19: ルートルーティングで「¡Hola, mundo!」を表示するよう変更する。
images/figures/goodbye_world
図1.20: ルートルーティングで「goodbye, world!」を表示するよう変更する。
  1. なお、この Ruby on Rails チュートリアルが最新版かどうかについては、本チュートリアルの公式サイト (http://railstutorial.org/) でチェックすることができます。特に、本チュートリアルをオフラインで読んでいる方は、オンライン版の Rails チュートリアルで最新版かどうか http://railstutorial.jp を一度ご確認してください。
  2. URIはUniform Resource Identifierの略です。それよりやや一般性の低いURLはUniform Resource Locatorの略です。URIは、要するに「ブラウザのアドレスバーにあるあれ」と考えればだいたい合っています。
  3. http://tryruby.org/ 
  4. http://railstutorial.jp/help 
  5. Windowsユーザーは、InstallRailsで推奨された手順に従った場合であっても、Railsインストーラが古いという警告が表示される可能性があります。また、最新のチュートリアルとの互換性が保たれない可能性もあります。
  6. たとえば、fooという名前の関数定義を見つけるには、「def foo」をグローバル検索します。
  7. https://c9.io/web/sign-up/free 
  8. 現時点のCloud9に含まれているRailsのバージョンは、最新のチュートリアルよりわずかに古く、互換性がありません。そのため、Railsを手動でインストールする必要があります。
  9. 3.1に示したように、実はinstallを省略できます。bundleコマンドそれ自体がbundle installのエイリアスであるためです。
  10. 通常、Webサイトは80番ポートで受信待ちしますが、このポートを使用するには特別な権限が必要になることが多いので、一般的な慣習として、制限があまりない大きめのポート番号 (いわゆるハイナンバーポート) を使用します。
  11. 実際には「Ctrlキーを押しながらcキーを押す」ことを意味します。このとき、大文字のCにするためにShiftキーも一緒に押す必要はありません。表記上の習慣により大文字で「Ctrl+C」と書かれているに過ぎません。
  12. チュートリアル本編ではこのファイルを修正することはありませんが、3.7.3.gitignoreファイルへの追加例があります。これは、3.7で行うオプションの詳細テスト設定の一部です。
  13. Gitリポジトリをビジュアル表示するには、AtlassianのSourceTreeアプリケーションが便利です。
  14. 詳細についてはPro GitのGitブランチに関する章を参照。
  15. Railsチュートリアルのサンプルアプリケーションでは気にする必要はありません。作りかけの恥ずかしいWebアプリケーションをネットにうっかり公開してしまわないだろうかと心配する方もいらっしゃるかと思いますが、それを防ぐための方法はいくつもありますのでご安心ください。1.5.4はその方法の1つです。
  16. “Engine X" と発音します。
  17. 一般的に、開発環境と本番環境は、データベースも含めてなるべく同じにしておく方が便利です。しかし本チュートリアルでは、教育的見地からあえてローカルではSQLite、本番ではPostgreSQLを使用するようにしています。詳細については3.1を参照。
  18. https://toolbelt.heroku.com/ 
  19. http://www.railstutorial.org/#email 
  20. 利用しているエディタによっては「invalid multibyte character」などのエラーメッセージが表示されることがあるかもしれませんが、気にすることはありません。このメッセージを表示したくないのであれば、エラーメッセージをググって対応してください。

第2章 Toyアプリケーション

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

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

2.1 アプリケーションの計画

はじめに、Toyアプリケーションをどのようなものにするのか、計画を立てましょう。1.3で説明したように、rails newコマンドでRailsのバージョン番号を指定して、アプリケーションの骨組みを生成するところから始めましょう。

$ cd ~/workspace
$ rails _4.2.2_ new toy_app
$ cd toy_app/

上のコマンドを実行すると「Could not find ’railties'」というエラーが表示される場合は、インストールしたRailsのバージョンが正しくない可能性があります。リスト1.1のとおりにコマンドを実行したかどうかを念のためダブルチェックしてください。(1.2.1で推奨されているクラウドIDEをご利用の場合は、この2つ目のアプリは1つ目のアプリと同じワークスペースに作成されるという点に注意する必要があります。2つ目のアプリのために別のワークスペースを作成する必要はありません。ファイルが表示されるようにするには、ファイルナビゲーターの歯車アイコンをクリックして [Refresh File Tree] をクリックします。)

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

リスト2.1 Toyアプリケーション用のGemfile
source 'https://rubygems.org'

gem 'rails',        '4.2.2'
gem 'sass-rails',   '5.0.2'
gem 'uglifier',     '2.5.3'
gem 'coffee-rails', '4.1.0'
gem 'jquery-rails', '4.0.3'
gem 'turbolinks',   '2.3.0'
gem 'jbuilder',     '2.2.3'
gem 'sdoc',         '0.4.0', group: :doc

group :development, :test do
  gem 'sqlite3',     '1.3.9'
  gem 'byebug',      '3.4.0'
  gem 'web-console', '2.0.0.beta3'
  gem 'spring',      '1.1.3'
end

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

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

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

$ bundle install --without production

最後に、GitでこのToyアプリケーションをバージョン管理下に置きます。

$ git init
$ git add -A
$ git commit -m "Initialize repository"

次に、Bitbucketで [Create] ボタンをクリックして新しいリポジトリを作成します(図 2.1)。続いて、生成したファイルをこの新しいリモートリポジトリにプッシュします。

$ git remote add origin git@bitbucket.org:<username>/toy_app.git
$ git push -u origin --all # リポジトリとブランチをすべてプッシュする
images/figures/create_demo_repo_bitbucket
図 2.1 Bitbucketにtoyアプリのリポジトリを作成する

最後に、デプロイの準備のため、リスト 1.8リスト 1.9.1で推奨した「Hello, world!」での手順と同様の手順を行っておきます。続いてこの変更をコミットし、Herokuにプッシュします。

$ git commit -am "Add hello"
$ heroku create
$ git push heroku master

(1.5で見た警告メッセージが表示されることがありますが、今は無視してください。詳しくは7.5で解説します)。Herokuアプリのアドレス以外は、1.18と同じになるはずです。

これで、アプリケーション自体を作成するための下準備が整いました。Webアプリケーションを作る際、アプリケーションで使用される構造を表すためのデータモデルを最初に作成しておくのが普通です。今回のToyアプリケーションでは、ユーザーと短いマイクロポストのみをサポートするマイクロブログを作成します。そこで、まずアプリケーションのユーザーで使用するモデルを作成 (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だけで構成されています2。しかし実際には、マイクロポストをユーザーと関連付ける (associate) 必要があります。そのため、マイクロポストの投稿者を記録するためのuser_idも追加します。これにより、データモデルは図2.3のようになります。

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

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

2.2 Usersリソース

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

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

$ rails generate scaffold User name:string email:string
      invoke  active_record
      create    db/migrate/20140821011110_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  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
      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によって自動的に主キーとしてデータベースに追加されるため、追加不要です)。

続いてToyアプリケーションの開発を進めるには、以下のように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を実行します (クラウド IDE など多くのシステムでは、bundle execなしでも rake を実行できますが、bundle execが必要なシステムもあるので、本書では念のため使用するようにしています)。

ここまでの手順が完了すると、以下のコマンドでローカルWebサーバーを別タブで実行できるようになります (1.7)4

$ rails server -b $IP -p $PORT    # Use only `rails server` if running locally

これで、1.3.2で説明したとおりにローカルサーバーが動作するはずです (クラウド IDE で作業している方は、IDE自体ではなく、必ずブラウザの別タブを使用してこのdevelopmentサーバーを開いてください)。

コラム2.1 Rake

Unixでは、ソースコードから実行用プログラムをビルドするために主にmakeというツールが使われてきました。多くのプログラマーが、肉体レベルにまで刻み込まれた以下のようなコマンドを実行して

$ ./configure && make && sudo make install

LinuxやMac OS Xなどで日夜コードをコンパイルしています。

RakeはいわばRuby版のmakeであり、Rubyで記述することのできる、makeのような言語です。Railsでは、Rakeを頻繁に使用しています。特に、データベースを背後に持つWebアプリケーション開発時に必要となる管理タスクで顕著です。rake db:migrateが一番よく使われるコマンドですが、rakeに-T dbオプションを付けて実行すると他にもさまざまなデータベースタスクが用意されているのがわかります。

$ bundle exec rake -T db

rakeで実行可能なタスクをすべて表示するには以下を実行します。

$ bundle exec rake -T

コマンドの多さに圧倒されがちですが、すべてのコマンドを今覚える必要はまったくありませんので、心配は無用です。Railsチュートリアルを最後まで読み終わる頃には、重要なコマンドは一通り使えるようになっていることでしょう。

2.2.1 ユーザーページを探索する

ブラウザでルートURL「/」(「スラッシュ」と読みます: 1.3.4参照) を開くと、1.9と同じRailsのデフォルトページが表示されますが、Usersリソースをscaffoldで生成したことで、ユーザー管理用のページが多数追加されている点が異なります。たとえば、/usersを表示すればすべてのユーザーの一覧が表示されますし、/users/newを表示すれば新規ユーザー作成ページが表示されます。このセクションでは以後、ユーザーに関連するページについて手短に説明します。その際、表2.1に記載されている、ページとURLの関係を参照するとわかりやすいと思います。

URL アクション 用途
/users index すべてのユーザーを表示するページ
/users/1 show id=1のユーザーを表示するページ
/users/new new ユーザーを新規作成するページ
/users/1/edit edit id=1のユーザーを編集するページ
表2.1: Usersリソースにおける、ページとURLの関係。

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

images/figures/demo_blank_user_index_3rd_edition
図2.4 Usersリソース (/users) ページの最初の状態

ユーザーを新規作成するには、図2.5newページを表示します (注: 簡単のため、ローカル開発中はアドレスの「http://0.0.0.0:3000」の部分やクラウドIDEを表す部分については今後省略しますので、各自で補ってください)。第7章では、このページをユーザー登録ページに転用します。

images/figures/demo_new_user_3rd_edition
図2.5 新規ユーザー作成ページ (/users/new)

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

images/figures/demo_show_user_3rd_edition
図2.6 ユーザー表示用のページ (/users/1)

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

images/figures/demo_edit_user_3rd_edition
図2.7 ユーザー編集用のページ (/users/1/edit)
images/figures/demo_update_user_3rd_edition
図2.8 情報が更新されたユーザー

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

images/figures/demo_user_index_two_3rd_edition
図2.9 2人目のユーザーが追加された一覧ページ (/users)

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

images/figures/demo_destroy_user_3rd_edition
図2.10 ユーザーを削除する

2.2.2 MVCの挙動

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

images/figures/mvc_detailed
図2.11 RailsにおけるMVC

2.11で行われている手順の概要を以下に示します。

  1. ブラウザから「/users」というURLのリクエストをRailsサーバーに送信する。
  2. 「/users」リクエストは、Railsのルーティング機構 (ルーター) によってUsersコントローラ内のindexアクションに割り当てられる。
  3. indexアクションが実行され、そこからUserモデルに、「すべてのユーザーを取り出せ」(User.all)と問い合わせる。
  4. Userモデルは問い合わせを受け、すべてのユーザーをデータベースから取り出す。
  5. データベースから取り出したユーザーの一覧をUserモデルからコントローラに返す。
  6. Usersコントローラは、ユーザーの一覧を@users変数 (@はRubyのインスタンス変数を表す) に保存し、indexビューに渡す。
  7. indexビューが起動し、ERB (Embedded RuBy: ビューのHTMLに埋め込まれているRubyコード) を実行して HTMLを生成 (レンダリング) する。
  8. コントローラは、ビューで生成されたHTMLを受け取り、ブラウザに返す5

上の流れをもう少し詳しく見てみることにします。最初にブラウザからのリクエストを見てみましょう。このリクエストは、アドレスバーにURLを入力したりリンクをクリックした時に発生します (図2.11の①)。リクエストはRailsルーターに到達し (②)、ここでURL (とリクエストの種類: コラム3.3参照) に基づいて適切なコントローラのアクションに割り当てられます (ディスパッチ)。ユーザーからリクエストされたURLをUsersリソースで使用するコントローラのアクションに割り当てる (マッピングする) コードは、リスト2.2のような感じになります。このコードはRailsのルーター設定ファイル (config/routes.rb) の中で使用され、URLとアクションの組み合わせ (表2.1) を効率よく設定することができます (:usersという一見奇妙な記法は、Ruby言語特有の「シンボル」と呼ばれるものです。詳細については4.3.3で説明します)。

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

それでは、このルーティングファイルを変更してみましょう。サーバーのルートURLにアクセスしたら、デフォルトのページの代わりにユーザー一覧を表示するようにします。つまり、「/」(スラッシュ) にアクセスしたら/usersを開くようにします。リスト1.10で以下の変更を行ったことを思い出してください。

# root 'welcome#index'

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

root 'application#hello'

これにより、ルートにアクセスするとApplicationコントローラ内のhelloアクションにルーティングされるようになったのでした。今回の場合は、Usersコントローラのindexアクションを使用したいので、リスト2.3のコードを元に書き換えてみましょう (既にApplicationコントローラにhelloアクションを追加していた方は、念のため削除しておくとよいかもしれません)。

リスト2.3 ルートからusersへのルーティングを追加する config/routes.rb
Rails.application.routes.draw do
  resources :users
  root 'users#index'
  .
  .
  .
end

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

リスト2.4 Usersコントローラの骨格 app/controllers/users_controller.rb
class UsersController < ApplicationController
  .
  .
  .
  def index
    .
    .
    .
  end

  def show
    .
    .
    .
  end

  def new
    .
    .
    .
  end

  def edit
    .
    .
    .
  end

  def create
    .
    .
    .
  end

  def update
    .
    .
    .
  end

  def destroy
    .
    .
    .
  end
end

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

HTTPリクエスト URL アクション 用途
GET /users index すべてのユーザーを表示するページ
GET /users/1 show id=1のユーザーを表示するページ
GET /users/new new ユーザーを新規作成するページ
POST /users create ユーザーを作成するアクション
GET /users/1/edit edit id=1のユーザーを編集するページ
PATCH /users/1 update id=1のユーザーを更新するアクション
DELETE /users/1 destroy id=1のユーザーを削除するアクション
表2.2 リスト2.2のUsersリソースに含まれるRESTfulなルーティング
コラム2.2 REpresentational State Transfer (REST)

Rails関連の書籍を読んでいると “REST” という略語をよく見かけます。これはREpresentational State Transferの略です。RESTは、インターネットそのものやWebアプリケーションなどの、分散/ネットワーク化されたシステムやアプリケーションを構築するためのアーキテクチャのスタイルの1つです。REST理論そのものはかなり抽象的ですが、RailsアプリケーションにおけるRESTとは、アプリケーションを構成するコンポーネント (ユーザーやマイクロポストなど) を「リソース」としてモデル化することを指します。これらのリソースは、リレーショナルデータベースの作成/読み取り/更新/削除 (Create/Read/Update/Delete: CRUD) 操作と、4つの基本的なHTTP requestメソッド (POST/GET/PATCH/DELETE) の両方に対応しています7 (HTTP requestメソッドの詳細については、3.2.1、特にコラム3.2で説明します)。

Rails開発者にとっては、RESTfulなスタイルを採用することで、作成すべきコントローラやアクションの決定が楽になります。作成(C)・読み取り(R)・更新(U)・削除(D)を行うリソースだけでアプリケーション全体を構成してしまうことすら可能です。ユーザーやマイクロポストなどに関しては自然にリソース化できるので問題ありません。第11章では、「ユーザーをフォローする」というやや複雑な課題をREST理論でモデリングします。

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

リスト2.5 Toyアプリケーションの簡潔なユーザー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.6にあります。驚くほどシンプルな内容ですが、継承 (2.3.4および4.4) によって多くの機能が備わっています。特に、Active RecordというRubyライブラリのおかげで、リスト2.6のUserモデルはUser.allというリクエストに対して、DB上のすべてのユーザーを返すことができます。

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

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

リスト2.7 indexアクションに対応しているビュー app/views/users/index.html.erb
<h1>Listing users</h1>

<table>
  <thead>
    <tr>
      <th>Name</th>
      <th>Email</th>
      <th colspan="3"></th>
    </tr>
  </thead>

<% @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.3 自動生成したUsersリソースの欠点

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

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

2.3 Micropostsリソース

Usersリソースを生成して内容を理解しましたので、今度はMicropostsリソースで同じことをやってみましょう。なお、この節全体について、Micropostsリソースを理解する際には2.2のuser要素と比較しながら進めることをお勧めします。実際、これらの2つのリソースはさまざまな面で似通っています。RailsのRESTful構造を身体に叩きこむには、繰り返し学ぶのが一番です。UsersリソースとMicropostsリソースの構造の類似点を理解することが、この章の主要な目的です。

2.3.1 マイクロポストのページを探検する

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

$ rails generate scaffold Micropost content:text user_id:integer
      invoke  active_record
      create    db/migrate/20140821012832_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  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
      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

(Springに関連したエラーが発生したら、同じコマンドをもう一度実行してみてください)。新しいデータモデルでデータベースを更新するには、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.8のようにMicropostsリソース用のルールが追加されました9。ユーザーの場合と同様、resources :micropostsというルーティングルールは、表2.3に示したようにマイクロポスト用のURIをMicropostsコントローラ内のアクションに割り当てます。

リスト2.8 Railsルートで使用するMicropostsリソース用のルール config/routes.rb
Rails.application.routes.draw do
  resources :microposts
  resources :users
  .
  .
  .
end
HTTPリクエスト URL アクション 用途
GET /microposts index すべてのマイクロポストを表示するページ
GET /microposts/1 show id=1のマイクロポストを表示するページ
GET /microposts/new new マイクロポストを新規作成するページ
POST /microposts create 新しいマイクロポストを作る
GET /microposts/1/edit edit id=1のマイクロポストを編集するページ
PATCH /microposts/1 update id=1のマイクロポストを更新するアクション
DELETE /microposts/1 destroy id1のマイクロポストを削除する
表2.3 リスト2.8のMicropostsリソースに含まれるRESTfulなルーティング

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

リスト2.9 Micropostsコントローラの骨格 app/controllers/microposts_controller.rb
class MicropostsController < ApplicationController
  .
  .
  .
  def index
    .
    .
    .
  end

  def show
    .
    .
    .
  end

  def new
    .
    .
    .
  end

  def edit
    .
    .
    .
  end

  def create
    .
    .
    .
  end

  def update
    .
    .
    .
  end

  def destroy
    .
    .
    .
  end
end

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

images/figures/demo_new_micropost_3rd_edition
図2.12 新しいマイクロポストの作成ページ (/microposts/new)

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

images/figures/demo_micropost_index_3rd_edition
図2.13 マイクロポストのindexページ (/microposts)

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

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

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

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

images/figures/micropost_length_error_3rd_edition
図2.14 マイクロポストの作成に失敗した場合のエラーメッセージ

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

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

リスト2.11 1人のユーザーに複数のマイクロポストがある。 app/models/user.rb
class User < ActiveRecord::Base
  has_many :microposts
end
リスト2.12 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がマイクロポストとユーザーを関連付けることができるようになっています。

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

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

$ rails console
>> first_user = User.first
=> #<User id: 1, name: "Michael Hartl", email: "michael@example.org",
created_at: "2014-07-21 02:01:31", updated_at: "2014-07-21 02:01:31">
>> first_user.microposts
=> [#<Micropost id: 1, content: "First micropost!", user_id: 1, created_at:
"2014-07-21 02:37:37", updated_at: "2014-07-21 02:37:37">, #<Micropost id: 2,
content: "Second micropost", user_id: 1, created_at: "2014-07-21 02:38:54",
updated_at: "2014-07-21 02:38:54">]
>> micropost = first_user.microposts.first    # Micropost.first would also work.
=> #<Micropost id: 1, content: "First micropost!", user_id: 1, created_at:
"2014-07-21 02:37:37", updated_at: "2014-07-21 02:37:37">
>> micropost.user
=> #<User id: 1, name: "Michael Hartl", email: "michael@example.org",
created_at: "2014-07-21 02:01:31", updated_at: "2014-07-21 02:01:31">
>> exit

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

2.3.4 継承の階層

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

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

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

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

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

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

2.3.5 アプリケーションをデプロイする

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

$ git status
$ git add -A
$ git commit -m "Finish toy app"
$ git push

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

この時点で、Toyアプリケーションを1.5のようにHerokuに展開してもかまいません。

$ git push heroku

(上のコマンド操作では、2.1のHerokuアプリを作成済みであることが前提です。アプリを作成していないのであれば、先にheroku creategit push heroku masterを実行してから上のコマンド操作を実行してください。)

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

$ heroku run rake db:migrate

このコマンドを実行すると、先ほど定義したユーザーとマイクロポストのデータモデルを使って、Heroku上のデータベースが更新されます。マイグレーションが完了すれば、Toyアプリを実際のPostgreSQLデータベースをバックエンドに配置した本番環境で利用できるようになっているはずです(2.18)。

images/figures/toy_app_production
図2.18: Toyアプリを本番環境で実行する

2.4 最後に

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

良い点

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

課題

  • レイアウトもスタイルも設定されていない
  • “Home” や “About” のような定番の静的なページがない
  • ユーザーがパスワードを設定できない
  • ユーザーが画像を置けない
  • ログインのしくみがない
  • セキュリティのためのしくみがまったくない
  • ユーザーとマイクロポストの自動関連付けが行われていない
  • Twitterのような「フォロワー (following)機能」や「フォロー中 (followed)機能」がない
  • マイクロポストをフィードできない
  • まともなテストがない
  • 理解が困難

本書では以後、このアプリの良い点を保ちつつ、弱点をひとつひとつ克服していきます。

2.4.1 本章のまとめ

  • Scaffold機能でコードを自動生成すると、Webのあらゆる部分からモデルデータにアクセスしてやりとりできるようになる。
  • Scaffoldは何よりも手っ取り早いのがとりえだが、これを元にRailsを理解するには向いていない。
  • RailsではWebアプリケーションの構成にMVC (Model-View-Controller) というモデルを採用している。
  • Railsが解釈するRESTには、標準的なURLセットと、データモデルとやりとりするためのコントローラアクションが含まれている。
  • Railsではデータのバリデーション (validation) がサポートされており、データモデルの属性の値に制限をかけることができる。
  • Railsには、さまざまなデータモデル同士を関連付けを定義するための組み込み関数が多数用意されている。
  • Railsコンソールを使用すると、コマンドラインからRailsアプリケーションとやりとりすることができる。

2.5 演習

: 『演習の解答マニュアル (英語)』にはRuby on Railsチュートリアルのすべての演習の解答が掲載されており、www.railstutorial.orgで原著を購入いただいた方には無料で配布しています (訳注: 解答は英語です)。

  1. リスト2.18のコードにはマイクロポストの存在確認用のバリデーションの追加方法が示されています。これは、マイクロポストが空欄にならないようにするためのものです。2.19と同じ表示になることを確認してください。
  2. リスト2.19の「FILL_IN」と記した部分を適切なコードに置き換え、Userモデルに名前属性およびメール属性が存在するかどうかをバリデーションできるようにしてください (2.20)。
リスト2.18: マイクロポストのコンテンツが存在しているかどうかのバリデーションコード app/models/micropost.rb
class Micropost < ActiveRecord::Base
  belongs_to :user
  validates :content, length: { maximum: 140 },
                      presence: true
end
images/figures/micropost_content_cant_be_blank
図2.19: マイクロポストの存在確認バリデーションの結果
リスト2.19: Userモデルに存在確認バリデーションを追加する app/models/user.rb
class User < ActiveRecord::Base
  has_many :microposts
  validates FILL_IN, presence: true
  validates FILL_IN, presence: true
end
images/figures/user_presence_validations
図2.20: Userモデルの存在確認バリデーションの結果
  1. 主な理由は、デフォルトのRailsページはHerokuで破損してしまうことが多く、そのままだとデプロイが成功したのか失敗したのかがわかりにくいためです。
  2. マイクロポストはその名のとおり短いのですが、string型の上限がかなり大きいのでそこに保存するとかなり余ってしまいます。text型ならもっと手頃で、かつ最大長を必要に応じて大きくすることもできます。
  3. scaffoldで指定する名前は、モデル名の命名の習慣に従って「単数形」にします。リソースやコントローラは「複数形」で表し、モデルは「単数形」で表します。従って、Scaffoldでは (Usersではなく) Userと指定します。
  4. railsスクリプトは、bundle execなしで動作する設計になっています。
  5. ビューは、(ApacheやNginxなどのWebサーバーを経由してはいるが) ブラウザにHTMLを直接返すと説明している文献もあります。私は、Railsの実際の実装とは無関係に、コントローラを情報の流れの中心となるハブとみなすことを好んでいます。
  6. 論文の正式なタイトル: Fielding, Roy Thomas. Architectural Styles and the Design of Network-based Software Architectures. Doctoral dissertation, University of California, Irvine, 2000. 
  7. 以前のRailsではデータの更新にHTTPのPUTリクエストを使用していましたが、HTTP標準に従えばPATCHリクエストの方が適切です。
  8. Userでscaffoldを実行した場合と同様に、scaffoldジェネレータではマイクロポストでもRailsモデルを単数形とする習慣に従います。実行したコマンドがgenerate Micropostと単数形になっていたのはこのためです。
  9. scaffoldで生成した実際のコードにはリスト2.8よりも多くの改行が追加されていることがあります。Rubyでは単なる改行は無視されるので、問題ありません。
  10. 実際のターミナル上では、Rubyのバージョンに応じてプロンプトが2.1.1 :001 >などと表示されることがありますが、例では>>に統一してあります。
  11. 「Ctrl-C」と同様に「Ctrl-D」と表記する慣習になっていますが、 実際に押すのは小文字のdなので、シフトキーを押し続ける必要はありません。

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

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

Rails はデータベースと連携して動的なWebサイトを開発するように設計されていますが、HTMLファイルだけで構成されている静的なページを作ることもできます。実際、Railsであえて静的なページを使用しておいて、後からほんの少し動的なコンテンツを追加することもできます。本章では、このような静的なページの作成について学んでいきます。本章ではそれと平行して、近年のプログラミングで不可欠となっている「自動化テスト」の雰囲気を掴んでいただきます。自動化テストを作成することで、コードが正しく動いていることが裏付けられます。さらに、良いテストを書くことで、自信をもってリファクタリングを行うことができます。たとえば、フォームの振る舞いを変更せずに、フォーム内で使われている関数を書き換えたいときに有用です。

3.1 セットアップ

2と同様に、新しいRailsプロジェクトを作成するところから始めます。今回はsample_appという名前にします (リスト3.1)1リスト3.1のコマンドを実行したときに「Could not find ’railties'」のようなエラーが発生した場合は、Railsのバージョンが正しくない可能性がありますので、リスト1.1のとおりに正しくコマンドを入力したかどうかを確認してください。

リスト3.1: サンプルアプリケーションを生成する
$ cd ~/workspace
$ rails _4.2.2_ new sample_app
$ cd sample_app/

(2.1でも説明したとおり、クラウドIDEをご利用の方は、このプロジェクトをこれまでの2つの章で作成したプロジェクトと同じワークスペースに置くことができます。このプロジェクトで特に新しいワークスペースを作成する必要はありません)。

次は、2.1と同じように、テキストエディタを使ってGemfileに必要なgemを書き足していきます。リスト3.2リスト1.5リスト2.1は基本的にまったく同じですが、testグループ内のgemだけが若干異なっています。ここではもう少し高度なテスト用オプションを設定しています (3.7) (: もしサンプルアプリケーションの開発で必要になるgemをすべて知りたい場合は、リスト11.67を参照してください。これが最終的なGemfileになります)。

リスト3.2: サンプルアプリケーション用のGemfile
source 'https://rubygems.org'

gem 'rails',        '4.2.2'
gem 'sass-rails',   '5.0.2'
gem 'uglifier',     '2.5.3'
gem 'coffee-rails', '4.1.0'
gem 'jquery-rails', '4.0.3'
gem 'turbolinks',   '2.3.0'
gem 'jbuilder',     '2.2.3'
gem 'sdoc',         '0.4.0', group: :doc

group :development, :test do
  gem 'sqlite3',     '1.3.9'
  gem 'byebug',      '3.4.0'
  gem 'web-console', '2.0.0.beta3'
  gem 'spring',      '1.1.3'
end

group :test do
  gem 'minitest-reporters', '1.0.5'
  gem 'mini_backtrace',     '0.1.3'
  gem 'guard-minitest',     '2.3.1'
end

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

前の2つの章と同様にbundle installを実行して、Gemfileで指定したgemをインストール、インクルードします。ただし、「--without production」オプション2を指定して、productionでしか使わないgemをインストールしないようにしておきます。

$ bundle install --without production

上のオプションを指定することで、PostgreSQL用のpg gemをdevelopment環境にインストールせず、代わりにSQLiteがdevelopment環境testing環境で使用されるようになります。Herokuでは、development環境とproduction環境とで異なるデータベースを使用することを非推奨としていますが、幸いにもこのサンプルアプリケーションでは両者の違いは生じません。また、SQLiteの方がPostgreSQLよりもローカルでのインストールや設定がずっと楽なので3、今回はことなるデータベースを使うことにします。ここで使用するGemfileで指定されているのと異なるバージョンのgem (Rails自身のgemを含む) をこれまでにインストールしていた場合は、以下のようにbundle updateを実行してgemを更新し、gemのバージョンを合わせておくとよいでしょう。

$ bundle update

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

$ git init
$ git add -A
$ git commit -m "Initialize repository"

最初のアプリケーションのときと同様に、まずはアプリケーションのルートディレクトリにあるREADMEファイルを更新して、具体的な作業内容をわかりやすく記入しておくことをおすすめします。最初にGitのコマンドでREADMEのファイル形式をRDocからMarkdownに変更します。

$ git mv README.rdoc README.md

続いてリスト3.3の内容をREADMEに記入します。

リスト3.3: サンプルアプリケーション向けに書き換えたREADME
# Ruby on Railsチュートリアル: サンプルアプリケーション

これは以下に基づいたサンプル・アプリケーションです
[*Ruby on Railsチュートリアル:
実例を使ってRailsを学ぼう*](http://railstutorial.jp/)
[Michael Hartl](http://www.michaelhartl.com/)著

最後に、変更をコミットします。

$ git commit -am "Improve the README"

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

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

$ git remote add origin git@bitbucket.org:<username>/sample_app.git
$ git push -u origin --all # リポジトリとブランチをすべてプッシュする

後で統合時に悩まずにすむよう、アプリをなるべく早い段階でHerokuにデプロイしておくとよいでしょう。12と同様に、リスト1.81.9の「Hello, world」の手順に従うことをお勧めします4。終わったら以下のように変更をコミットしてHerokuにプッシュします。

$ git commit -am "Add hello"
$ heroku create
$ git push heroku master

(1.5のときと同じように警告メッセージが表示されることがありますが、無視して構いません。この警告は7.5で解決する予定です)。これで、Herokuアプリのアドレス以外は1.18のとおりに表示されるはずです。

この後も、本チュートリアルを進めながらアプリケーションをこまめにプッシュ/デプロイすることをおすすめします。こうすることでリモートバックアップにもなり、production環境でのエラーを早めに確認することもできます (訳注: 最後にまとめてプッシュ/デプロイすると問題が同時多発して解決に手間取ることが考えられます)。なお、Herokuに展開するときにエラーが発生した場合は、以下のコマンドを実行して本番環境のログを取得してください。このログは、問題を特定するときに役立ちます。

$ heroku logs

: 今後Herokuで何らかの本番アプリケーションを運用する予定があるなら、7.5のproduction用Webサーバーの設定に必ず従ってください。

3.2 静的ページ

3.1の準備がすべて完了したら、いよいよサンプルアプリケーションの開発に取りかかりましょう。この節では、Railsのアクションビューを作成して、静的なHTMLのみのページを動的なページに作り変えるための最初の手順を進めます5。Railsのアクションはコントローラ (1.3.3で言うMVCの「C」) の中に置かれます。コントローラには、目的に沿って互いに関連したアクションが置かれます。コントローラについては2でも簡単に触れましたが、6で説明するREST アーキテクチャを読むと理解が深まります。一言で言うと、コントローラとは (基本的に動的な) Webページの集合を束ねるコンテナのことです。現在どのディレクトリで作業しているかがわからなくなった場合は、1.3 (図 1.4)を再度参照して、Rails のディレクトリ構造を確認してください。この節では、主にapp/controllersディレクトリやapp/viewsディレクトリ内で作業を進めます。

1.4.4で学んだことを思い出しましょう。Gitを使用する場合は、masterブランチでずっと作業するのではなく、その都度トピックブランチを作成して作業するのがよい習慣です。Gitでバージョン管理を行っているのであれば、以下のコマンドを実行して、静的なページ用のトピックブランチをチェックアウトしましょう。

$ git checkout master
$ git checkout -b static-pages

(1行目は、確実にmasterブランチに切り替えるために行っています。これにより、2行目のstatic-pagesトピックブランチがmasterから作成されるようになります。もしすでにmasterブランチにいる場合は、1行目のコマンドを実行する必要はありません)。

3.2.1 静的なページの生成

静的なページの作成は、2でscaffold生成に使用したgenerateスクリプトで、コントローラを生成することから始めます。このコントローラは静的なページを扱うためにしか使わないので、コントローラ名を「Static Pages」に決め、表記をキャメルケースStaticPagesにします。続いて、Homeページ、Helpページ、Aboutページに使用するアクションもそれぞれ作成することにし、アクション名はすべて小文字のhomehelpaboutにします。generateスクリプトではアクション名をまとめて指定することもできるので、コマンドラインでHomeページとHelpページ用のアクションもまとめて生成することにします。なお、Aboutページだけは学習のため、あえてコマンドラインでは作成せず、3.3で手動で追加することにします。これらの要素を盛り込んだStaticPagesコントローラ生成コマンドと実行結果をリスト3.4に示します。

リスト3.4: StaticPagesコントローラを生成する
$ rails generate controller StaticPages home help
      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  test_unit
      create    test/controllers/static_pages_controller_test.rb
      invoke  helper
      create    app/helpers/static_pages_helper.rb
      invoke    test_unit
      create      test/helpers/static_pages_helper_test.rb
      invoke  assets
      invoke    coffee
      create      app/assets/javascripts/static_pages.js.coffee
      invoke    scss
      create      app/assets/stylesheets/static_pages.css.scss

追伸: rails grails generateコマンドの短縮形であり、Railsでサポートされている多数の短縮形のひとつです (3.1)。本チュートリアルではわかりやすさを重んじているため、こうしたコマンドは短縮せずに表記していますが、現実のRails開発者はほぼ間違いなく3.1の短縮形を常用しています。

完全なコマンド 短縮形
$ rails server $ rails s
$ rails console $ rails c
$ rails generate $ rails g
$ bundle install $ bundle
$ rake test $ rake
表3.1: Railsで使える短縮形の例

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

$ git status
$ git add -A
$ git commit -m "Add a Static Pages controller"
$ git push -u origin static-pages

最後のコマンドでは、static-pagesトピックブランチをBitbucketにプッシュしています。以後は、単に以下を実行するだけで同じプッシュが行われるようになります。

$ git push

上のコミット〜プッシュの流れは、著者が実際の開発でよく使っていたパターンに基づいていますが、ここから先は途中でこのような指示をいちいち書くことはしませんので、各自こまめにプッシュするようにしてください。

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

$ rails generate controller static_pages ...

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

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

コラム3.1 元に戻す方法

どれほど十分に気を付けていたとしても、Railsアプリケーションの開発中に何か失敗してしまうことはありえます。ありがたいことに、Railsにはそのような失敗をカバーする機能がいくつもあります。

一般的なシナリオの1つは、生成したコードを元に戻したい場合です。たとえば、コントローラを生成した後で、もっといいコントローラ名を思い付き、生成したコードを削除したくなった場合などです。リスト3.4のように、Railsはコントローラ以外にも関連ファイルを大量に生成するので、生成されたコントローラファイルを削除するだけでは元に戻りません。自動生成されたコードを完全に元に戻すには、新規作成されたファイルを削除するほかに、既存のファイルに挿入されたコードも削除する必要があります (実際、2.22.3でも説明したように、rails generateを実行するとルーティングのroutes.rbファイルも自動的に変更されるので、これも元に戻さなくてはなりません)。このようなときは、「generate」という言葉に因んで、rails destroyというコマンドを実行することで元に戻すことができます。たとえば次の2つのコマンドは、自動生成と、それに対応する取り消し処理の例です。

  $ rails generate controller StaticPages home help
  $ rails destroy  controller StaticPages home help

なお第6章でも、以下のようにモデルを自動生成する方法を紹介します。

  $ rails generate model User name:string email:string

モデルの自動生成についても、同様の方法で元に戻すことができます。

  $ rails destroy model User

(上のコマンドからわかるように、モデル名以外の引数は不要です。その理由については第6章で説明します)。

また、第2章でも簡単に紹介しましたが、マイグレーションの変更を元に戻す方法も用意されています。詳細は第6章で説明します。簡単に言うと、まず以下のコマンドでデータベースのマイグレーションを変更できます。

  $ bundle exec rake db:migrate

以下のコマンドで1つ前の状態に戻すこともできます。

  $ bundle exec rake db:rollback

最初の状態に戻したい場合は、以下のコマンドを使います。

  $ bundle exec rake db:migrate VERSION=0

既にお気付きの方もいると思いますが、マイグレーションは逐次的に実行され、それぞれのマイグレーションに対してバージョン番号が付与されます。したがって、上記の0を別の数字に置き換えることによって、指定したバージョンの状態に戻すことができます。

開発中に袋小路に迷い込んでしまった場合でも、これらの機能を使えば元の状態を復元できます。

リスト3.4のようにStaticPagesコントローラを生成すると、(config/routes.rb)ファイルが自動的に更新されます (1.3.4のときと同様です)。このルーティングファイルはルーターの実装を受け持ち (2.11)、URLとWebページの対応関係を定義します。このルーティングファイルはRailsのconfigディレクトリの下に置かれます。このディレクトリには、Railsの設定ファイルがまとめて置かれます (3.1)。

images/figures/config_directory_3rd_edition
図3.1 サンプルアプリケーションのconfigディレクトリの内容

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

リスト3.5: StaticPagesコントローラ内のhomeアクションとhelpアクションで使用するルーティング config/routes.rb
Rails.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.2) が対応しているメソッドの1つです。今回の場合は、StaticPagesコントローラ内にhomeアクションを追加したので、/static_pages/homeにアクセスすることでページを取得 (GET) できるようになりました。結果を確認するには、1.3.2に従って以下のようにRailsのdevelopmentサーバーを起動します。

$ rails server -b $IP -p $PORT    # ローカルサーバーの場合は`rails server`だけを実行する

/static_pages/homeにアクセスして結果を表示します (3.2)。

images/figures/raw_home_view_3rd_edition
図3.2 /static_pages/homeにアクセスした結果
コラム3.2 GETやその他のHTTPメソッドについて

HTTP (HyperText Transfer Protocol) には4つの基本的な操作があり、それぞれGETPOSTPATCHDELETEという4つの動詞に対応づけられています。クライアント (通常、FirefoxやSafariなどのWebブラウザ) とサーバー (ApacheやNginxなどのWebサーバー) は、上で述べた4つの基本操作を互いに認識できるようになっています(ローカル環境でRailsアプリケーションを開発しているときは、クライアントとサーバーが同じコンピュータ上で動いていますが、一般的には、それぞれ別のコンピュータで動作しているという点を理解しておいてください)。Railsを含む多くのWebフレームワークは、HTTPの各操作を発展させたREST アーキテクチャの影響を受けています。第2章でも簡単に触れましたが、第7章では、より深い内容について学びます。

GET は、最も頻繁に使用されるHTTP操作で、主にWeb上のデータを読み取る際に使われます。「ページを取得する」という意味のとおり、ブラウザはgoogle.comwikipedia.orgなどのWebサイトを開くたびにGETリクエストをサイトに送信します。Railsアプリケーションでは、POSTリクエストは何かを作成するときによく使われます (なお本来のHTTPでは、POSTを更新に使ってもよいとしています)。たとえば、ユーザー登録フォームで新しいユーザーを作成するときは、POSTリクエストを送信します。他にも、PATCHDELETEという2つの操作があり、それぞれサーバー上の何かを更新したり削除したりするときに使われます。これら2つの操作は、GETPOSTほどは使用されていません。これは、ブラウザがPATCHとDELETEをネイティブでは送信しないからです。しかし、Ruby on Railsなどの多くのWebフレームワークは、ブラウザがこれらの操作のリクエストを送信しているかのように見せかける技術 (偽装) を駆使して、PATCHとDELETEという操作を実現しています。結果として、Railsはこの4つのHTTPリクエスト (GETPOSTPATCHDELETE) を全てサポートできるようになりました。

このページがどのようにして表示されるのかを理解するために、まずはテキストエディタで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アクションを定義しています。2.3.4で説明したように、山括弧<は、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.3.3で説明したMVCのVに相当) を出力します。今回の場合、homeアクションが空になっているので、/static_pages/homeにアクセスしても、単に対応するビューが出力されるだけです。では、ビューはどのように出力されるのでしょうか。また、どのビューが表示されるのでしょうか。

リスト3.4をもう一度注意深く読んでみると、アクションとビューの関係性について理解できるでしょう。homeアクションは、home.html.erbというビューに対応しています。.erbの詳細については3.4で説明しますが、ファイル名に.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.2.2 静的なページの調整

3.4からは (ほんの少しだけ) 動的なコンテンツを追加しますが、リスト3.7リスト3.8で見たように、重要なのは「Railsのビューの中には静的なHTMLがある」という点です。これは、Railsの知識が無くてもHomeページやHelpページを修正できることを意味しています。以下のリスト3.9リスト3.10がその一例です。

リスト3.9: HomeページのHTMLを修正する app/views/static_pages/home.html.erb
<h1>Sample App</h1>
<p>
  This is the home page for the
  <a href="http://www.railstutorial.org/">Ruby on Rails Tutorial</a>
  sample application.
</p>
リスト3.10: HelpページのHTMLを修正する 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 section</a>.
  To get help on this sample app, see the
  <a href="http://railstutorial.jp"><em>Ruby on Rails Tutorial</em>
  book</a>.
</p>

リスト3.9リスト3.10の結果をそれぞれ3.33.4に示します。

images/figures/custom_home_page
図3.3 修正されたHomeページ
images/figures/custom_help_page_3rd_edition
図3.4 修正されたHelpページ

3.3 テストから始める

3.2.2でサンプルアプリのHomeページとHelpページを作成して中身も書き加えたので、今度はAboutページを同様に追加します。何らかの変更を行う際には、常に「自動化テスト」を作成して、機能が正しく実装されたことを確認する習慣をぜひ身に付けましょう。アプリケーションを開発しながらテストスイートをみっちり作成しておけば、いざというときのセーフティネットにもなり、それ自体がアプリケーションのソースコードの「実行可能なドキュメント」にもなります。テストを作成するということは、その分コードを余分に書くことになりますが、正しく行えば、むしろテストがないときよりも確実に開発速度がアップします。テストが揃っていれば、バグを追うために余分な時間を使わずに済むためです。そんなふうにうまくいくとは信じられない人もいるかもしれませんが、一度でもテスト作成が上達すれば間違いなくこのとおりになります。だからこそ、テスト作成の習慣をできるだけ早いうちに身につけることが重要なのです。

テストが重要であるという点ではRails開発者の意見はほぼ一致していますが、細かい点では異論が生じているのも確かです。特に、テスト駆動開発 (TDD)6 (テストの手法のひとつ: 最初に「正しいコードがないと失敗するテスト」を書き、次に本編のコードを書いてそのテストがパスするようにする) の是非については、当分議論が終わりそうにありません。筆者も悩んだ末、Ruby on Railsチュートリアルではこの点について「とにかく〜すべし」的な原理主義を避けることにしました。テストに関しては、原則として手軽かつ直感的なアプローチを採用し、必要に応じてTDDに切り替えるようにしています (コラム3.3)。

コラム 3.3 結局テストはいつ行えばよいのか

それではいつ、どんなふうにテストを行えばよいのでしょうか。この点を理解するために、テストを行う目的をもう一度確認してみましょう。著者は、テストには以下の3つのメリットがあると考えます。

  1. テストが揃っていれば、機能停止に陥るような回帰バグ (regression: 以前のバグが再発したり機能追加/変更の副作用が生じたりすること、先祖返りとも言う) を防止できる。
  2. テストが揃っていれば、コードを安全にリファクタリング (機能を変更せずにコードを改善すること) できる。
  3. テストコードは、アプリケーションコードから見ればクライアントとして動作するので、アプリケーションの設計やシステムの他の部分とのインターフェイスを決めるときにも役に立つ。

上の3つのメリットは、テストを先に書かなくても得ることができますが、それでもテスト駆動開発 (TDD) という手法をいつでも使えるようにしておけば、間違いなく多くの場面で役に立ちます。テストの手法やタイミングは、ある意味テストをどのぐらいすらすら書けるかで決まると言ってよいでしょう。たいていの開発者は、テストを書くのに慣れてくるとテストを先に書くようになります。その他にも、アプリケーションのコードと比べてテストがどのぐらい書きにくいか、必要な機能をどのぐらい正確に把握しているか、その機能が将来廃止される可能性がどのぐらいあるかによっても異なってくるでしょう。

こういうときのために、「テスト駆動」にするか「一括テスト」にするかを決める目安となるガイドラインがあると便利です。著者の経験を元に、以下のようにまとめてみました。

  • アプリケーションのコードよりも明らかにテストコードの方が短くシンプルになる (=簡単に書ける) のであれば、テストを先に書けるようになることを目指す。
  • 期待している動作がまだ固まりきっていないのであれば、先にアプリケーションのコードを書き上げ、続いて期待する動作をテストコードで記述することを目指す。
  • セキュリティが最重要課題であれば、セキュリティモデルでエラーが発生した場合のテストを最初に書くべき。
  • バグを見つけたら、そのバグを再現するテストを真っ先に書き、回帰バグを防ぐ体制を整えてからアプリケーションのコードの修正に取りかかる。
  • 将来変更の可能性が少しでもあるコード (HTML構造の細部など) があれば必ずテストを書く。
  • リファクタリングの前には必ずテストを書き、エラーを起こしそうなコードや、特に止まってしまいそうなコードを集中的にテストする。

上のガイドラインに従う場合、現実には最初にコントローラやモデルのテストを書き、続いて統合テスト (モデル/ビュー/コントローラにまたがる機能テスト) を書く、ということになります。また、不安定な要素が特に見当たらないアプリケーションや、(主にビューが) 頻繁に改定される可能性の高いアプリケーションのコードを書くときには、思い切ってテストを省略してしまうこともないわけではありません。

本書における主要なテストは、コントローラテスト (この節より)、モデルテスト (6より)、統合テスト (7より) の3つです。統合テストでは、ユーザーがWebブラウザでアプリケーションとやりとりする操作をシミュレートできるので特に強力です。統合テストは最終的にテスティングにおける最も主要な武器となりますが、まずは取っ付きやすいコントローラテストから始めることにしましょう。

3.3.1 最初のテスト

それではサンプルアプリケーションのAboutページの作成に取りかかります。やってみるとわかりますが、このページでは大したことは行わないので、このテストは驚くほど短く単純です。早速コラム3.3のガイドラインに沿って、テストを先に書くことにしましょう。続いてそのテストを実行して「失敗」することを確認し、実際のアプリケーションコードを書きます。

初めて書くテストがいきなり「テスト先行」というのは、Ruby on Railsの知識がある程度以上必要なため、少々ハードルが高い面もあります。今の段階でテストを書かせようとすると、尻込みしてしまう人もいるかもしれません。しかしご心配なく。面倒な部分は既にRailsが全部面倒を見てくれています。rails generate controller (リスト3.4) を実行した時点でテストファイルがちゃんと作成されているので、それを利用しましょう。

$ ls test/controllers/
static_pages_controller_test.rb

生成されたテストを見てみましょう (リスト3.11)。

リスト3.11: StaticPagesコントローラのデフォルトのテスト GREEN test/controllers/static_pages_controller_test.rb
require 'test_helper'

class StaticPagesControllerTest < ActionController::TestCase

  test "should get home" do
    get :home
    assert_response :success
  end

  test "should get help" do
    get :help
    assert_response :success
  end
end

現時点では、上のリスト3.11の文法をいきなり理解する必要はありません。今は「このファイルにはテストが2つ書かれている」ことを認識していただければ十分です。その2つのテストは、リスト3.4で生成したコントローラの2つのアクションであるHomeとHelpに対応して生成されたものです。それぞれのテストでは、アクションをgetして正常に動作することを確認します。この確認は「アサーション」(assertion: 主張、断言) と呼ばれる手法で行います。getは、HomeページやHelpページがいわゆる「GETリクエストを受け付ける」普通のWebページであるということを示します (コラム3.2)。その次の「response:success」は、実際にはHTTP のステータスコード (ここでは200 OK) を表します。つまり、以下のようなテストは

test "should get home" do
  get :home
  assert_response :success
end

言葉で表すと「Homeページのテスト。GETリクエストをhomeアクションに対して発行 (=送信) せよ。そうすれば、リクエストに対するレスポンスは[成功]になるはず。」となります。

テスティングサイクルの最初の一回しに取りかかる前に、まずは現在のテストスイートをそのまま実行して、問題なくパスすることを確認しておきます。テストの実行には、以下のようにrakeユーティリティを使用します (コラム2.1)7

リスト 3.12: GREEN
$ bundle exec rake test
2 tests, 2 assertions, 0 failures, 0 errors, 0 skips

テストスイートは期待どおりパス GREEN します (パスしたときにも色を表示できるようにするには、3.7.1のminitestレポーターをオプションで追加する必要があります)。ところで、テストの実行にはある程度時間がかかります。これには2つの要因が絡んでいます: (1) Spring serverを起動してRails環境を事前読み込みするのに時間がかかる。ただしこれは最初の1回だけです。(2) Rubyそのものの起動に時間がかかる (2番目の要因については、3.7.3で紹介するGuardを導入することで改善できます)。

3.3.2 Red

コラム3.3で解説したように、テスト駆動開発のサイクルは「失敗するテストを最初に書く」「次にアプリケーションのコードを書いてパスさせる」「必要ならリファクタリングする」のように進みます。多くのテストツールでは、テストの失敗を「レッド」、成功したときを「グリーン」で表します。ここから、このサイクルを「レッド・グリーン・リファクタリング」と呼ぶこともあります。これに従って最初のサイクルを完了させましょう。まず失敗するテストを書いてREDになるようにします。テストをGREENにするのは3.3.3、リファクタリングは3.4.3で行います8

サイクルの記念すべき第一歩はAboutページ用の失敗するテストを書くことです。リスト3.11を参考にすれば、正しいテストコードを何となく想像できると思います。正しいテストコードをリスト3.13に示します。

リスト3.13: Aboutページのテスト RED test/controllers/static_pages_controller_test.rb
require 'test_helper'

class StaticPagesControllerTest < ActionController::TestCase

  test "should get home" do
    get :home
    assert_response :success
  end

  test "should get help" do
    get :help
    assert_response :success
  end

  test "should get about" do
    get :about
    assert_response :success
  end
end

リスト3.13のハイライト行を見ると、他のHomeページ用テストやHelpページ用テストとほとんど同じであることがわかります。違いは「home」や「help」の部分が「about」に変わっている点だけです。

テストを実行すると、期待どおり失敗します。

リスト3.14: RED
$ bundle exec rake test
3 tests, 2 assertions, 0 failures, 1 errors, 0 skips

3.3.3 Green

テストがめでたく失敗した (RED) ので、今度はこのテストのエラーメッセージを頼りにテストがパスする (GREEN) ようにコードを書くことで、Aboutページを実装します。

失敗したテストのエラーメッセージをもっと詳しく見ていきましょう9

リスト3.15: RED
$ bundle exec rake test
ActionController::UrlGenerationError:
No route matches {:action=>"about", :controller=>"static_pages"}

このエラーメッセージによれば、「指定されたアクション/コントローラの組み合わせに一致するルーティングが見当たらない」とあります。つまりルーティングファイルを修正する必要があるということです。リスト3.5のときと同じ要領で変更を行った結果をリスト3.16に示します。

リスト3.16: about用のルートを追加する RED config/routes.rb
Rails.application.routes.draw do
  get 'static_pages/home'
  get 'static_pages/help'
  get 'static_pages/about'
  .
  .
  .
end

リスト3.16のハイライト行では、/static_pages/aboutというURLへのGETリクエストが来たら、StaticPagesコントローラのaboutアクションに渡すようRailsに指示しています。

修正が終わったらテストスイートを再度実行します。まだREDのままです。しかし今度はメッセージが少し変わりました。

リスト3.17: RED
$ bundle exec rake test
AbstractController::ActionNotFound:
The action 'about' could not be found for StaticPagesController

このエラーメッセージから、「StaticPagesコントローラにaboutアクションがない」ということがわかります。リスト3.6homehelpと同じようにaboutアクションを追加します (リスト3.18)。

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

  def home
  end

  def help
  end

  def about
  end
end

今度はどうでしょう。まだREDですが、エラーメッセージがまた少し変わりました。

$ bundle exec rake test
ActionView::MissingTemplate: Missing template static_pages/about

今度はテンプレートがないようです。Railsではテンプレートといえばすなわち「ビュー」のことです。3.2.1で説明したように、homeというアクションはhome.html.erbというビューに関連付けられます。このビューはapp/views/static_pagesにあるので、ここにabout.html.erbというファイルを作ればよさそうです。

ファイルの作成方法はシステムの設定によってさまざまですが、たいていのテキストエディタでは、ディレクトリをCtrl+クリックすればコンテキストメニューに「New File」のようなメニューが表示されます。あるいはエディタの[File]メニューでファイルを作成して、このディレクトリに保存しても構いません。個人的にはUnixのtouchコマンドでファイルを作成するのがかっこいいと思います。

$ touch app/views/static_pages/about.html.erb

touchコマンドは本来ファイルやディレクトリのタイムスタンプだけを更新するためのコマンドなのですが、ファイルが存在しない場合には空ファイルを作成するという一種の副作用があります (クラウドIDEをご利用の場合は、touchでファイル作成後に1.3.1のようにファイルツリーの更新が必要な場合があります)。

とにかくabout.html.erbを正しいディレクトリに作成できたので、リスト3.19のとおりにコードを入力します。

リスト3.19: Aboutページのコード GREEN app/views/static_pages/about.html.erb
<h1>About</h1>
<p>
  The <a href="http://www.railstutorial.org/"><em>Ruby on Rails
  Tutorial</em></a> is a
  <a href="http://railstutorial.jp">book</a> and
  <a href="http://screencasts.railstutorial.org/">screencast series</a>
  to teach web development with
  <a href="http://rubyonrails.org/">Ruby on Rails</a>.
  This is the sample application for the tutorial.
</p>

今度はrake testの結果はGREENになるはずです。

リスト3.20: GREEN
$ bundle exec rake test
3 tests, 3 assertions, 0 failures, 0 errors, 0 skips

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

images/figures/about_us_3rd_edition
図3.5 作成したAboutページ (/static_pages/about)

3.3.4 リファクタリング

テストがGREENになったので、安心してコードをリファクタリングできるようになりました。アプリケーションの開発が進むと、コードのどこからともなく「腐敗臭」が漂い始めます。コードや記法の統一が崩れて読みづらくなる、クラスやメソッドが何百行にも膨れ上がって読む気を削がれる、なぜこのコードがここにあるのか最早誰もその理由を思い出せなくなる、同じコードがあちこちにコピペされて少しずつ書き換えられ手に負えなくなる、などです。コンピュータにしてみればどんなに汚らしいコードであろうと、そこにあるがままに実行するだけですが、人間はそういうわけにはいきません。こまめにリファクタリングを繰り返してコードを常にすみずみまで美しくコンパクトに保ち、他の開発者や未来の自分の開発意欲を阻喪することのないようにしなければなりません。このサンプルアプリは生まれたてなので、今のところリファクタリングの必要な箇所はほぼどこにも見当たりません。しかし「一匹いれば30匹いると思え」、コードの腐敗臭はどんな小さな隙間からも忍び寄ってきます。こまめなリファクタリングの習慣をできるだけ早いうちに身につけるためにも、少々無理やりに3.4.3から始めることにします。

3.4 少しだけ動的なページ

静的なページのアクションやビューをいくつか作成できたので、今度はそれをほんの少しだけ動的にしてみましょう。ページの内容に応じて、ページのタイトルを自ら書き換えて表示するようにします。タイトルを自動で変えるぐらいのことが真の動的コンテンツと呼べるかどうかは議論の余地があると思いますが、いずれにしろこのページは、第7章で紹介する本格的な動的コンテンツの基礎となります。

ここでの目標は、Homeページ、Helpページ、Aboutページをそれぞれ編集し、最終的にページごとに異なるタイトルを表示することです。ここではビューの<title>タグの内容を変更します。多くのブラウザでは、titleタグの内容をブラウザウィンドウの上部にウィンドウタイトルとして表示します。titleタグは、いわゆるSEO (search engine optimization: 検索エンジン最適化) においても重要な役割を果たします。今度は「レッド・グリーン・リファクタリング」のサイクルをすべて行うことにします。ページタイトルの簡単なテストを書き (RED)、3つのページにタイトルを追加し (GREEN)、レイアウトファイルを活用してコードの重複を解決します (リファクタリング)。本節の終わりまでに、3つの静的ページのタイトルを「<ページ名> | Ruby on Rails Tutorial Sample App」という形式に変更します。「<ページ名>」の部分がページに応じて変わります (3.2)。

前述のrails newコマンド (リスト3.1) を実行すると、レイアウトもデフォルトで作成されます。ここでは学習のため、一時的に以下のようにファイル名を変更します。

$ mv app/views/layouts/application.html.erb layout_file   # temporary change

普通は、実際のアプリケーション開発時に上のような操作を行うことはありません。ここでは、レイアウトファイルの役割をよりわかりやすく説明するために、最初にレイアウトファイルを無効にしています。

ページ 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"
表3.2 サンプルアプリケーションの (ほぼ) 静的なページ。

3.4.1 タイトルをテストする (Red)

ページタイトルを追加するために、典型的なWebページの構造を今一度おさらいしておきましょう (リスト3.21)。

リスト3.21: Webページの典型的なHTML構造
<!DOCTYPE html>
<html>
  <head>
    <title>Greeting</title>
  </head>
  <body>
    <p>Hello, world!</p>
  </body>
</html>

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

3.2の各タイトルについて簡単なテストを書きます (リスト3.13)。このテストで使用しているassert_selectメソッドでは、特定のHTMLタグが存在するかどうかをテストします (この種のアサーションメソッドはその名から「セレクタ」と呼ばれることもあります)11

assert_select "title", "Home | Ruby on Rails Tutorial Sample App"

上のセレクタは、<title>タグ内に「Home | Ruby on Rails Tutorial Sample App」という文字列があるかどうかをチェックします。同じ要領で3つの静的ページを書き換えます (リスト3.22)。

リスト3.22: StaticPagesコントローラのタイトルをテストする RED test/controllers/static_pages_controller_test.rb
require 'test_helper'

class StaticPagesControllerTest < ActionController::TestCase

  test "should get home" do
    get :home
    assert_response :success
    assert_select "title", "Home | Ruby on Rails Tutorial Sample App"
  end

  test "should get help" do
    get :help
    assert_response :success
    assert_select "title", "Help | Ruby on Rails Tutorial Sample App"
  end

  test "should get about" do
    get :about
    assert_response :success
    assert_select "title", "About | Ruby on Rails Tutorial Sample App"
  end
end

(上のテストコードで繰り返し使われている「Ruby on Rails Tutorial Sample App」という文字列を一刻も早くリファクタリングしたくてたまらない方には、3.6の演習をおすすめします。)

リスト3.22どおりにテストを作成すると、テストスイートはREDになります。

リスト3.23: RED
$ bundle exec rake test
3 tests, 6 assertions, 3 failures, 0 errors, 0 skips

3.4.2 タイトルを追加する (Green)

今度は各ページにタイトルを追加して、3.4.1のテストがパスするようにしましょう。リスト3.21の基本HTML構造をカスタムのHomeページ (リスト3.9) に追加すると、リスト3.24のようになります。

リスト3.24: 完全なHTML構造を備えたHomeページのビュー RED app/views/static_pages/home.html.erb
<!DOCTYPE html>
<html>
  <head>
    <title>Home | Ruby on Rails Tutorial Sample App</title>
  </head>
  <body>
    <h1>Sample App</h1>
    <p>
      This is the home page for the
      <a href="http://www.railstutorial.org/">Ruby on Rails Tutorial</a>
      sample application.
    </p>
  </body>
</html>

このページの表示を3.6に示します12

images/figures/home_view_full_html
図3.6 タイトルが付いたHomeページ

Helpページ (リスト3.10) やAboutページ (リスト3.19) についても、同じ要領でリスト3.25リスト3.26のようなコードに変更します。

リスト 3.25: 完全なHTML構造を備えたHelpページのビュー RED app/views/static_pages/help.html.erb
<!DOCTYPE html>
<html>
  <head>
    <title>Help | Ruby on Rails Tutorial Sample App</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
      section</a>.
      To get help on this sample app, see the
      <a href="http://railstutorial.jp"><em>Ruby on Rails
      Tutorial</em> book</a>.
    </p>
  </body>
</html>
リスト3.26: 完全なHTML構造を備えたAboutページのビュー GREEN app/views/static_pages/about.html.erb
<!DOCTYPE html>
<html>
  <head>
    <title>About | Ruby on Rails Tutorial Sample App</title>
  </head>
  <body>
    <h1>About</h1>
    <p>
      The <a href="http://www.railstutorial.org/"><em>Ruby on Rails
      Tutorial</em></a> is a
      <a href="http://railstutorial.jp">book</a> and
      <a href="http://screencasts.railstutorial.org/">screencast series</a>
      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>

これでテストスイートはGREENになるはずです。

リスト3.27: GREEN
$ bundle exec rake test
3 tests, 6 assertions, 0 failures, 0 errors, 0 skips

3.4.3 レイアウトと埋め込みRuby (Refactor)

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

  • ページのタイトルがどれもほぼ同じ (完全にではないが)。
  • 「Ruby on Rails Tutorial Sample App」という文字が3つのタイトルで繰り返し使われている。
  • HTMLの構造全体が各ページで重複している。

同じコードを繰り返すことはRubyの「DRY」(Don't Repeat Yourself: 繰り返すべからず) という原則に反します。この節では、繰り返しを追放してコードをDRY (=よく乾かす) にしましょう。最後に3.4.2のテストを実行して、タイトルを壊していないことを確認します。

上の話と一見矛盾するようですが、最初にコードを若干追加して、現在は「ほぼ」同じになっているページのタイトルを「完全に」同じにしておきます。この方が、コードの重複を一括で取り除けるからです。

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

リスト3.28: タイトルにERBコードを使用したHomeページのビュー GREEN app/views/static_pages/home.html.erb
<% provide(:title, "Home") %>
<!DOCTYPE html>
<html>
  <head>
    <title><%= yield(:title) %> | Ruby on Rails Tutorial Sample App</title>
  </head>
  <body>
    <h1>Sample App</h1>
    <p>
      This is the home page for the
      <a href="http://www.railstutorial.org/">Ruby on Rails Tutorial</a>
      sample application.
    </p>
  </body>
</html>

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

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

上のコードでは<% ... %>という記法が使用されており、その中からRailsのprovide関数を呼び出しています。関数の引数では、"Home"という文字列と:titleというラベルを関連付けています14。そしてタイトルの部分では、上の記法と連携する「<%= ... %>」というよく似た記法を使用し、その中でRubyのyield関数を呼び出しています15。この関数によって、テンプレートのその部分に実際のタイトルが挿入されます。

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

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

3.4.2のテストを実行してこの改修を確認すれば、今度もGREENになるはずです。

リスト3.29: GREEN
$ bundle exec rake test
3 tests, 6 assertions, 0 failures, 0 errors, 0 skips

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

リスト3.30: タイトルにERBコードを使用したHelpページのビュー GREEN app/views/static_pages/help.html.erb
<% provide(:title, "Help") %>
<!DOCTYPE html>
<html>
  <head>
    <title><%= yield(:title) %> | Ruby on Rails Tutorial Sample App</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
      section</a>.
      To get help on this sample app, see the
      <a href="http://railstutorial.jp"><em>Ruby on Rails
      Tutorial</em> book</a>.
    </p>
  </body>
</html>
リスト3.31: タイトルにERBコードを使用したAboutページのビュー GREEN app/views/static_pages/about.html.erb
<% provide(:title, "About") %>
<!DOCTYPE html>
<html>
  <head>
    <title><%= yield(:title) %> | Ruby on Rails Tutorial Sample App</title>
  </head>
  <body>
    <h1>About</h1>
    <p>
      The <a href="http://www.railstutorial.org/"><em>Ruby on Rails
      Tutorial</em></a> is a
      <a href="http://railstutorial.jp">book</a> and
      <a href="http://screencasts.railstutorial.org/">screencast series</a>
      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>

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

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

こうして見ると、HTMLの構造はtitleタグの内容も含めてどのページも完全に同じです。異なる点があるとすれば、bodyタグの内側のコンテンツだけです。

これはもうリファクタリングしてHTMLの重複した構造をDRYにするしかないでしょう。ご想像のとおり、Railsにはそのためにapplication.html.erbという名前のレイアウトファイルがあります。最初3.4でこのレイアウトファイルの名前をわざわざ変えておきましたが、いよいよ以下のコマンドでファイル名を元に戻すことにしましょう。

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

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

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

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

リスト3.32: サンプルアプリケーションのレイアウト GREEN app/views/layouts/application.html.erb
<!DOCTYPE html>
<html>
  <head>
    <title><%= yield(:title) %> | Ruby on Rails Tutorial Sample App</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 %>

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

もちろん、リスト3.28リスト3.30リスト3.31のビューには、レイアウトと重複するHTMLがまだ残っているので、それらを削除して、内部のコンテンツだけ残します。この改修が終わると、 リスト3.33リスト3.34リスト3.35のように実に簡潔で美しいコードになります。

リスト3.33: HTML構造を削除したHomeページ GREEN 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://www.railstutorial.org/">Ruby on Rails Tutorial</a>
  sample application.
</p>
リスト3.34: HTML構造を削除したHelpページ GREEN 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 section</a>.
  To get help on this sample app, see the
  <a href="http://railstutorial.jp"><em>Ruby on Rails Tutorial</em>
  book</a>.
</p>
リスト3.35: HTML構造を削除したAboutページ GREEN app/views/static_pages/about.html.erb
<% provide(:title, "About") %>
<h1>About</h1>
<p>
  The <a href="http://www.railstutorial.org/"><em>Ruby on Rails
  Tutorial</em></a> is a
  <a href="http://railstutorial.jp">book</a> and
  <a href="http://screencasts.railstutorial.org/">screencast series</a>
  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ページの表示は以前と変わりませんが、コードの重複が大きく削減されました。

この節で行ったようなちっぽけなリファクタリングですら、実際にやってみると大小さまざまなエラーが発生します。ベテラン開発者ほどこのことを骨の髄まで理解しており、どんな小さなリファクタリングでもあなどったりしません。テストスイートをきちんと整備しておくことがいかに重要であるか、皆さんにもご理解いただけると思います。開発のごく初期の段階なら全ページを目視でひとつひとつ確認して回ることもできるかもしれませんが、そんな方法ではじきに手に負えなくなります。このアプリでは必要なテストスイートが整備されているので、今度もGREENになることを確認するだけでOKです。

リスト3.36: GREEN
$ bundle exec rake test
3 tests, 6 assertions, 0 failures, 0 errors, 0 skips

もちろん厳密に言えば、テストがパスしたというだけではそのコードが本当に正しいのかどうかの証明にはなりません。しかし正しいコードに確実に近づくことができ、正しい可能性も上がります。何よりも、テストがあれば今後発生するバグを防ぐためのセーフティネットになります。

3.4.4 ルーティングの設定

サイトのページのカスタマイズが終わって、テストスイートも軌道に乗ってきたので、今のうちにアプリケーションルートのルーティングを設定しておきましょう。1.3.42.2.2でやったように、ルーティングを設定するにはroutes.rbファイルを編集して、ルート「/」とWebページを結び付けます。結び付ける相手はHomeページです (3.1でApplicationコントローラにhelloアクションを追加した場合は、今のうちにアクションを削除しておくことをおすすめします)。変更結果をリスト3.37に示します。ここでは、リスト3.5getルールを以下のコードに置き換えています。

root 'static_pages#home'

上のルールに置き換えると、static_pages/homeというURLからstatic_pages#homeというコントローラ/アクションへの対応付けが変わり、ルート「/」へのGETリクエストがStaticPagesコントローラのhomeアクションにルーティングされます。変更後のルーティングファイルを3.7に示します。リスト3.37のコードにすると、static_pages/homeにアクセスしても動作しなくなります。

リスト3.37: HomeページをルートURLに設定する config/routes.rb
Rails.application.routes.draw do
  root 'static_pages#home'
  get  'static_pages/help'
  get  'static_pages/about'
end
images/figures/home_root_route
図3.7 ルートURLにアクセスするとHomeページが表示される

3.5 最後に

はたから見れば、皆さんがこの章で結局どんなことを達成できたのかさっぱりわからないかもしれません。静的なページをあれこれ作り替えて、ほぼ静的なページにしただけなのでしょうか。もちろんそんなことはありません。皆さんはこの章でRailsのコントローラ、アクション、ビューの開発をひととおり行ったことで、これから動的なコンテンツをどしどしサイトに追加するための準備がすっかり整ったのです。残る課題は、皆さんがこのチュートリアルをいかに最後までやりぬくか、それだけであると言ってよいでしょう。

次の章に進む前に、差分をコミットしてmasterブランチにマージしておきましょう。3.1では、静的ページの開発のためのGitブランチを用意しました。ここまでの作業内容をコミットしていない場合、作業の区切りをつけるためにもコミットしましょう。

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

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

$ git checkout master
$ git merge static-pages

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

$ git push

また、この時点でHerokuにデプロイしてみてもよいでしょう。

$ bundle exec rake test
$ git push heroku

デプロイする前にテストを走らせていますが、こういった習慣を身につけておくと開発に役立ちます。

3.5.1 本章のまとめ

  • 新しいRailsアプリケーションをゼロから作成したのはこれで3度目。今回も必要なgemのインストール、リモートリポジトリへのプッシュ、production環境まで行った。
  • コントローラを新規作成するためのrailsのスクリプトはrails generate controller ControllerName <action name (省略可)>。訳注: コントローラ名はキャメルケース、アクション名はスネークケースにする。
  • 新しいルーティングはconfig/routes.rbファイルで定義する。
  • Railsのビューでは、静的HTMLの他にERB (埋め込みRuby: Embedded RuBy) も使用できる。
  • 常に自動化テストを使用して新機能開発を進めることで、自信を持ってリファクタリングできるようになり、回帰バグもいちはやくキャッチできるようになる。
  • テスト駆動開発では「レッド・グリーン・リファクタリング」サイクルを繰り返す。
  • Railsのレイアウトでは、アプリケーションのページの共通部分をテンプレートに置くことでコードの重複を解決することができる。

3.6 演習

: 『演習の解答マニュアル (英語)』にはRuby on Railsチュートリアルのすべての演習の解答が掲載されており、www.railstutorial.orgで原著を購入いただいた方には無料で配布しています (訳注: 解答は英語です)。

以後本チュートリアルの演習を解く際には、以下のように演習用トピックブランチを別途作成してそこで行うことをおすすめします。

$ git checkout static-pages
$ git checkout -b static-pages-exercises

トピックブランチを分けておくことで、チュートリアル本編との食い違いを避けることができます。

満足のゆく解ができたら、リモートリポジトリにプッシュしてもよいでしょう (リモートリポジトリがある場合)。

<solve first exercise>
$ git commit -am "Eliminate repetition (solves exercise 3.1)"
<solve second exercise>
$ git add -A
$ git commit -m "Add a Contact page (solves exercise 3.2)"
$ git push -u origin static-pages-exercises
$ git checkout master

(最後の行では、この後の開発準備のためにmasterブランチをチェックアウトしていますが、チュートリアル本編への影響を避けるため、演習で行った変更はmasterにマージしていません)。今後の章では、ブランチやコミットメッセージはもちろん異なりますが、基本的なアイディアは同じです。

  1. StaticPagesコントローラのテスト (リスト3.22) にも重複があることにお気付きでしょうか。特に「Ruby on Rails Tutorial Sample App」を全てのタイトルテストでそのまま使っています。専用のsetup関数 (テストの設定用関数、個別のテストの前に必ず毎回実行される) を使用してこの重複を解消し、テスト修正後もリスト3.38のテストがGREENになることを確認します (なお、リスト3.38ではインスタンス変数 (2.2.24.4.5) と文字列の式展開 (4.2.2) を使用しています)。
  2. サンプルアプリケーションにContact (問い合わせ先) ページを作成してください17リスト3.13を参考にして、/static_pages/contactというURLのページに「Contact | Ruby on Rails Tutorial Sample App」というタイトルが存在するかどうかを確認するテストを最初に作成しましょう。3.3.3でAboutページにやったのと同じように、Contactページにもリスト3.39のコンテンツを表示しましょう (リスト3.39にはリスト3.38のような修正は行われていないので、そのままコピペしても動きません)。
リスト3.38: 基本タイトルを含めたStaticPagesコントローラのテスト GREEN test/controllers/static_pages_controller_test.rb
require 'test_helper'

class StaticPagesControllerTest < ActionController::TestCase

  def setup
    @base_title = "Ruby on Rails Tutorial Sample App"
  end

  test "should get home" do
    get :home
    assert_response :success
    assert_select "title", "Home | #{@base_title}"
  end

  test "should get help" do
    get :help
    assert_response :success
    assert_select "title", "Help | #{@base_title}"
  end

  test "should get about" do
    get :about
    assert_response :success
    assert_select "title", "About | #{@base_title}"
  end
end
リスト3.39: Contactページで使用するコード app/views/static_pages/contact.html.erb
<% provide(:title, "Contact") %>
<h1>Contact</h1>
<p>
  Contact the Ruby on Rails Tutorial about the sample app at the
  <a href="http://www.railstutorial.org/#contact">contact page</a>.
</p>

3.7 高度なセットアップ

この追加の節は、Ruby on Railsチュートリアルスクリーンキャストシリーズ (原著者の主催する有料スクリーンキャスト: 英語のみ) で使用するテスト用設定について解説します。大きく3つに分かれます: 高度なパス/失敗表示 (3.7.1)、テスト失敗時の大量のバックトレースメッセージをフィルタするユーティリティ (3.7.2)、ファイルの変更を検出して、必要なテストだけを自動実行してくれる「自動テスト実行ユーティリティ」(3.7.3)。この節で参考までに示したコードはそれなりに高度なので、今すぐ理解できるようになる必要はありません。

この節の変更はmasterブランチで行う必要があります。

$ git checkout master

3.7.1 minitestレポーター

Railsのデフォルトのテストで、必要に応じてREDGREENを表示するためには、リスト3.40のコードをテスト用ヘルパーファイルに追加するだけです18。このコードでは、リスト3.2で追加したminitest-reporters gemを利用しています。

リスト3.40: REDGREENを表示できるようにする test/test_helper.rb
ENV['RAILS_ENV'] ||= 'test'
require File.expand_path('../../config/environment', __FILE__)
require 'rails/test_help'
require "minitest/reporters"
Minitest::Reporters.use!

class ActiveSupport::TestCase
  # Setup all fixtures in test/fixtures/*.yml for all tests in alphabetical
  # order.
  fixtures :all

  # Add more helper methods to be used by all tests here...
end

この変更により、Cloud IDE上の表示がREDからGREENに変わります (3.8)。

images/figures/red_to_green
図3.8: Cloud IDE上で表示をREDからGREENに変える

3.7.2 Backtrace silencer

テストが失敗した時に、テスト失敗の道筋をアプリケーション全体にわたってたどるスタックトレース (バックトレース) が表示されます。バックトレースは問題を追跡するうえでは非常に便利なのですが、クラウドIDEなど一部のシステムでは、このトレースがgemの依存関係やRails自身にまで及ぶことがあります。そうなると大量のスタックトレースが出力されて非常に不便です。gemの依存関係を調べているのでもなければ、開発しているアプリケーションで問題の原因を追跡中に大量のメッセージが出力されても、邪魔なだけです。

こうした不要な出力行を除去するために、バックトレースをフィルタします。これを行うにはmini_backtrace gem (リスト3.2) とbacktrace silencerを組み合わせます。クラウドIDEの場合、そうした不要な行ではほとんどの場合rvm (=Ruby Version Manager) という文字がパスに含まれているので、これを利用してフィルタします (リスト3.41)。

リスト3.41: RVMをフィルタするbacktrace silencerを追加する config/initializers/backtrace_silencers.rb
# Be sure to restart your server when you modify this file.

# You can add backtrace silencers for libraries that you're using but don't
# wish to see in your backtraces.
Rails.backtrace_cleaner.add_silencer { |line| line =~ /rvm/ }

# You can also remove all the silencers if you're trying to debug a problem
# that might stem from framework code.
# Rails.backtrace_cleaner.remove_silencers!

リスト3.41のコメント冒頭にあるように、backtrace silencerを追加した後は必ずRails webサーバーを再起動してください。

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

rake testコマンドは、テストをする度にコマンドラインに移動して手動でコマンドを実行しなければならない点が面倒です。この不便さを取り除くために、Guardを使ってテストを自動的に実行させるようにしてみましょう。Guardは、ファイルシステムの変更を監視し、たとえばstatic_pages_test.rbファイルなどを変更すると自動的にテストを実行してくれるツールです。また、テストファイルだけでなく、home.html.erbファイルが変更されるとstatic_pages_test.rbが自動的に実行されるようにGuardを設定することもできます。

実はすでに、リスト3.2Gemfileguard gemをアプリケーション内に取り込んでいます。したがって、あとは初期化するだけで動かすことができます。

$ bundle exec guard init
Writing new Guardfile to /home/ubuntu/workspace/sample_app/Guardfile
00:51:32 - INFO - minitest guard added to Guardfile, feel free to edit it

統合テストとビューが更新されたら自動的に適切なテストが実行されるように、生成されたGuardfileを編集します (リスト3.42)。(やや長くて応用的な設定なので、リスト3.42をコピペしてしまった方がよいでしょう)

リスト3.42: カスタマイズしたGuardfile.
# Defines the matching rules for Guard.
guard :minitest, spring: true, all_on_start: false do
  watch(%r{^test/(.*)/?(.*)_test\.rb$})
  watch('test/test_helper.rb') { 'test' }
  watch('config/routes.rb')    { integration_tests }
  watch(%r{^app/models/(.*?)\.rb$}) do |matches|
    "test/models/#{matches[1]}_test.rb"
  end
  watch(%r{^app/controllers/(.*?)_controller\.rb$}) do |matches|
    resource_tests(matches[1])
  end
  watch(%r{^app/views/([^/]*?)/.*\.html\.erb$}) do |matches|
    ["test/controllers/#{matches[1]}_controller_test.rb"] +
    integration_tests(matches[1])
  end
  watch(%r{^app/helpers/(.*?)_helper\.rb$}) do |matches|
    integration_tests(matches[1])
  end
  watch('app/views/layouts/application.html.erb') do
    'test/integration/site_layout_test.rb'
  end
  watch('app/helpers/sessions_helper.rb') do
    integration_tests << 'test/helpers/sessions_helper_test.rb'
  end
  watch('app/controllers/sessions_controller.rb') do
    ['test/controllers/sessions_controller_test.rb',
     'test/integration/users_login_test.rb']
  end
  watch('app/controllers/account_activations_controller.rb') do
    'test/integration/users_signup_test.rb'
  end
  watch(%r{app/views/users/*}) do
    resource_tests('users') +
    ['test/integration/microposts_interface_test.rb']
  end
end

# Returns the integration tests corresponding to the given resource.
def integration_tests(resource = :all)
  if resource == :all
    Dir["test/integration/*"]
  else
    Dir["test/integration/#{resource}_*.rb"]
  end
end

# Returns the controller tests corresponding to the given resource.
def controller_test(resource)
  "test/controllers/#{resource}_controller_test.rb"
end

# Returns all tests for the given resource.
def resource_tests(resource)
  integration_tests(resource) << controller_test(resource)
end

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

guard :minitest, spring: true, all_on_start: false do

この行ではGuardからSpringサーバーを使用して読み込み時間を短縮しています (SpringはRailsの機能のひとつです)。また、開始時にテストスイートをフルで実行しないようGuardに指示しています。

Guard使用時のSpringとGitの競合を防ぐには、.gitignoreファイルにspring/ディレクトリを追加します。.gitignoreはGitの設定ファイルのひとつで、ここで指定されたファイルはGitレポジトリに追加されなくなります。クラウドIDEでは以下の操作を行います。

  1. ナビゲーションパネルの右上のにある歯車アイコンをクリックします (3.9)。
  2. [Show hidden files] を選択して、アプリケーションのルートディレクトリにある.gitignoreファイルを表示します (3.10).
  3. .gitignoreファイル (3.11) をダブルクリックして開き、リスト3.43のように更新します。
images/figures/file_navigator_gear_icon
図3.9 ファイルナビゲーターにある (あまり目立たない) ギアのアイコン
images/figures/show_hidden_files
図3.10 ファイルナビゲーター内の隠しファイルを表示する
images/figures/gitignore
図3.11 隠れている.gitignoreファイルを表示する
リスト3.43: .gitignoreにSpringを追加する
# See https://help.github.com/articles/ignoring-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

# Ignore Spring files.
/spring/*.pid

Springサーバーは本節の執筆時点では若干不安定な点が残っていて、Springのプロセスが起動したまま多数残留すると、テストのパフォーマンスが低下してしまうことがあります。テストの実行が異常に遅くなってきたと感じたら、システムプロセスをチェックしてSpringを必要に応じてkillするとよいでしょう (コラム3.4)。

コラム3.4 Unixのプロセス

LinuxやOS XなどのUnix系システムは、ユーザータスクやシステムタスクはプロセス (process) と呼ばれる一種のコンテナの内部で実行されます。システム上で動いているすべてのプロセスは、psコマンドにauxオプションを付けて実行することで確認できます。

  $ ps aux

プロセスの種類を指定してフィルタするには、psの結果をUnixの「パイプ」|でつないで、パターンマッチャーであるgrepに渡します。

  $ ps aux | grep spring
  ubuntu 12241 0.3 0.5 589960 178416 ? Ssl Sep20 1:46
  spring app | sample_app | started 7 hours ago

表示結果の中で重要なのは最初の列の数値です。これはプロセスid、略してpidと呼ばれるものです。不要なプロセスを排除するには、killコマンドでpidを指定し、Unixのkillコード (ここでは15ですがシステムによって異なります) を発行します。

  $ kill -15 12241

行儀の悪いRailsサーバーなどのプロセスをkillする際には、上のようにひとつずつkillすることをおすすめします。Railsサーバーのpidを知るには、ps aux | grep server)などと実行します。しかし時には特定の名前を持つプロセスをまとめてkillしたいこともあります。しつこいspringプロセスたちをひとつずつkillするのは大変面倒です。一括killを行うには、最初にspringコマンドそのものでプロセスを停止しておく必要があります。

  $ spring stop

このコマンドが効かないことも多いので、そのときはいよいよpkillコマンドでspringの名前を指定してkillします。

  $ pkill -15 -f spring

開発中に動作がおかしくなったりプロセスがフリーズしていると思えたら、すぐにps auxで状態を確認し、kill -15 <pid>pkill -15 -f <プロセス名>でクリーンアップしましょう。

Guardの設定が完了したら、新しいターミナルを開き (1.3.2でやったようにRailsサーバーのターミナルと別にするのがポイントです)、以下をコマンドラインで実行します

$ bundle exec guard

リスト3.42のルールは本チュートリアルに最適化したものなので、たとえばコントローラのファイルを変更すると、Guardは即座にそれを検出して、そのコントローラの統合テストを自動実行します。テストを変更ファイルだけではなく、フルで実行したい場合は、guard>プロンプトでReturnキーを押します (このとき、Springサーバーに接続できないなどのエラーが表示されることがあります。問題を修正するには、もう一度Returnキーを押します)。

Guardを終了するにはCtrl-Dキーを押します。Guardに他のマッチャーを追加する方法については、リスト3.42の例、Guard READMEGuard wikiを参照してください。

  1. クラウドIDEをお使いの場合は「Goto Anything」コマンド (ファイル名の一部を入力するだけでその場所にジャンプする) が重宝します。「hello」アプリ「toy」アプリ「sample」アプリには同じファイル名が多数あるため、これらのアプリを同じプロジェクトに置くとファイルを見つけにくくなることがあります。たとえば「Gemfile」というファイル名を検索すると、GemfileGemfile.lockを含め、候補が6つも表示されてしまいます。そこで、この先に進む前に先の2つのアプリを思い切って削除しておくとよいでしょう。アプリを削除するには、workspaceディレクトリに移動してrm -rf hello_app/ toy_app/コマンドを実行します(1.1)。これらのアプリを既にBitbucketのリポジトリにプッシュしてあるなら、それを利用していつでもアプリを復元できます (その必要があればですが)。
  2. この--without productionオプションを一度実行すると「記憶される」ことにご注意ください。つまり、次回以降bundle installを実行すると、このオプションが暗黙で自動適用されます。
  3. 最終的には皆さんがPostgreSQLをdevelopment環境にインストールして設定できるようになるのが理想ですが、今は時期尚早であると考えます。実際に必要が生じたときは「install configure postgresql <自分のシステム>」や「rails postgresql setup」でググって各自挑戦してみてください (クラウドIDEの場合は<自分のシステム>にUbuntuと指定します)。
  4. 2でも指摘したとおり、主な理由は、デフォルトのRailsページはHerokuで破損してしまうことが多く、そのままだとデプロイが成功したのか失敗したのかがわかりにくいためです。
  5. ここで静的なページを作るために採用した方法は、おそらく最もシンプルな方法です。ただし他にも方法はあります。最適な方法は状況によって異なり、たとえば極めて多数の静的なページを1つのStaticPagesコントローラだけまかなおうとすると重荷になる可能性があります。今回はいくつかの静的なページを作るだけなので、重荷にはなりません。もし多数の静的なページが必要になる場合は、high_voltage gem を調べてみてください。なお、この問題には (やや古いですが) 有益な議論があります。 詳しくは hasmanythroughに投稿された記事「simple pages」 (英語) を読んでみてください。
  6. Rails生みの親であるDavid Heinemeier Hansson (通称DHH) の有名な記事『TDDは死んだ。テスティングに栄光あれ』(英語) を参照。
  7. 2.2でも説明したように、システム環境によってはbundle execが追加不要なこともあります。クラウドIDE (1.2.1) も追加不要なシステムのひとつです。しかしここでは省略せずにコマンドを記述しています。著者の場合、原則としてbundle execは追加せずに実行し、うまくいかないときだけbundle execを追加して様子を見る、ということをよく行っています。
  8. rake testはデフォルトで、テストの失敗を赤色で表示しますが、テストがパスしても緑色で表示しません。色もちゃんと表示したい場合は3.7.1をご覧ください。
  9. システムによっては、ソースコードのエラーパスを追跡する「スタックトレース」または「バックトレース」と呼ばれるメッセージが大量に表示されることがあります。この場合、かなり上にスクロールする必要があるかもしれません。バックトレース出力を絞り込んで不要な行が表示されないようにしたい場合は、3.7.2をご覧ください。
  10. HTMLの仕様は時とともに変わる可能性があると思っておく方がよいでしょう。今後もブラウザでなるべく正しくページを表示できるように、doctypeを明示的に宣言しています。追加属性がまったくないシンプルな「<!DOCTYPE html>」は、最新標準であるHTML5の特徴です。
  11. minitestで利用できるアサーションのリストについては、Railsガイドの「Rails テスティングガイド」をご覧ください。
  12. 本書のスクリーンショットでは原則としてGoogle Chromeを使用していますが、Chromeのタブはタイトルを表示しきれないので、3.6では代わりにSafariを使用しています。
  13. 2番目に人気のテンプレートとしてHamlがあり (注意: "HAML"ではありません) 、筆者は個人的にHamlの方が気に入っています。残念ながら十分に普及していないため、初級者向けチュートリアルの採用は見送りました。
  14. Railsでの開発経験者であれば、この時点でcontent_forの使用を検討すると思いますが、残念ながらAsset Pipelineと併用すると正常に動作しないことがあります。provide関数はcontent_forの代替です。
  15. Rubyを勉強したことのある方であれば、Railsはブロックの内容をyieldしていると推測することでしょう。そして、その推測はおそらく正しいでしょう。しかし、Rails開発のためにこれらの詳細を知る必要はありません。
  16. コミット時に「マージするとSpringのプロセスID (pid) ファイルが上書きされる可能性があります」のようなエラーメッセージが表示される場合は、コマンドラインでrm -f *.pidを実行してpidファイルを削除してください。
  17. この演習は5.3.1の節に解答があります。
  18. リスト3.40のコードには、シングルクオーテーション (') とダブルクオーテーション (") の両方が含まれています。これは、rails newで生成されたコードはシングルクオーテーションを使っていますが、minitestレポーターのドキュメントではダブルクオーテーションを使っていることが原因です。Rubyでは、この2つのクオーテーションを併用することが一般的です。詳しくは4.2.2で解説します。 

第4章 Rails風味のRuby

この章では、第3章で使用した例を基に、Railsにおいて重要となるRubyのさまざまな要素について探っていくことにしましょう。Rubyは巨大な仕様を持つ言語ですが、幸い、Rails開発者にとって必要な知識は比較的少なくて済みます。また、一般のRuby入門書で扱っている内容とも多少異なっています。この章の目的は、「Rails風味のRuby」というものについての確固たる基盤を、皆さんのこれまでの言語経験に関わらず提供することです。この章には多くの話題が盛り込まれていますが、一度読んだだけで理解する必要はまったくありません。今後もこの章には頻繁に立ち戻って参照します。

4.1 動機

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

前章の終わりでは、Railsのレイアウトを使用してビューでの重複を取り除くために、ほぼ静的なページを単に更新したにとどまりました (リスト4.1)。これは、リスト3.32と同じものです。

リスト4.1: サンプルアプリケーションのWebサイトのレイアウト app/views/layouts/application.html.erb
<!DOCTYPE html>
<html>
  <head>
    <title><%= yield(:title) %> | Ruby on Rails Tutorial Sample App</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のタイトル行の部分に注目しましょう。

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

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

<% provide(:title, "Home") %>
<h1>Sample App</h1>
<p>
  This is the home page for the
  <a href="http://www.railstutorial.org/">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
      page_title + " | " + base_title
    end
  end
end

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

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

上のコードを以下に置き換えます。

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

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

リスト4.3: full_titleヘルパーを使ったWebサイトのレイアウト GREEN 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ページのタイトル確認用にテストを更新する RED test/controllers/static_pages_controller_test.rb
require 'test_helper'

class StaticPagesControllerTest < ActionController::TestCase
  test "should get home" do
    get :home
    assert_response :success
    assert_select "title", "Ruby on Rails Tutorial Sample App"
  end

  test "should get help" do
    get :help
    assert_response :success
    assert_select "title", "Help | Ruby on Rails Tutorial Sample App"
  end

  test "should get about" do
    get :about
    assert_response :success
    assert_select "title", "About | Ruby on Rails Tutorial Sample App"
  end
end

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

リスト4.5: RED
$ bundle exec rake test
3 tests, 6 assertions, 1 failures, 0 errors, 0 skips

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

リスト4.6: ページタイトルをカスタマイズせずに表示するHomeページ GREEN app/views/static_pages/home.html.erb
<h1>Sample App</h1>
<p>
  This is the home page for the
  <a href="http://www.railstutorial.org/">Ruby on Rails Tutorial</a>
  sample application.
</p>

この時点で、テストはパスするはずです。

リスト4.7: GREEN
$ bundle exec rake test

注意: これまではrake testを実行した結果の一部 (成功結果や失敗結果など) も載せていましたが、紙幅の関係から、今後は実行結果を省略します。

Rails開発経験者にとっては、リスト4.2のコードはスタイルシートをインクルードするのと大差ない単純なものですが、ここにもRubyの重要な概念が多数含まれています。モジュール、メソッド定義、任意のメソッド引数、コメント、ローカル変数の割り当て、論理値 (boolean)、制御フロー、文字列の結合、そして戻り値です。これらの概念についても、この章ですべて説明します。

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

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

クラウドIDEをご利用の場合は、オススメのirbの設定があります。シンプルなテキストエディタ「nano」を使って、ホームディレクトリに「 .irbrc」ファイルを作ってみましょう (リスト4.8)。

$ nano ~/.irbrc

リスト4.8の設定を使用すると、irbのプロンプトがより簡潔な表示に置き換えられ、irbの邪魔な自動インデント機能がオフになります。

リスト4.8: irbの設定ファイルを追加する ~/.irbrc
IRB.conf[:PROMPT_MODE] = :SIMPLE
IRB.conf[:AUTO_INDENT_MODE] = false

上の設定はあくまでオススメなので、リスト4.8を追加しなくても、以下のように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
>> ""         # 空の文字列
=> ""
>> "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"     # 文字列の画面出力
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 \\."

最後にもう一度申し上げます。ほとんどの場合、シングルクォートとダブルクォートのどちらを使おうと大きな違いはありません。実際、一般のソースコードでは、明確な理由もなく両者が混用されているケースをよく見かけます。以上でRubyの文字列に関する説明は終わりです。あ、言い忘れていたことがありましたね。「Rubyの世界へようこそ!」

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

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

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

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

オブジェクトに渡すメッセージは、一般にはメソッドと呼ばれます。メソッドの実体は、そのオブジェクトに定義された関数です4。文字列は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"

条件文を2つ以上含めたい場合は、elsif (else + if) という文を使います。

>> if s.nil?
>>   "The variable is nil"
>> elsif s.empty?
>>   "The string is empty"
>> elsif s.include?("foo")
>>   "The string includes 'foo'"
>> end
=> "The string includes 'foo'"

論理値は、&&||!演算子によって結合することもできます。それぞれ「and」「or」「not」という予約語で書くこともできます (訳注: Rubyでは、後者のandとorは、前者の&&と||演算子より処理の優先順位が低くなっています)。

>> 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: undefined method `empty?' for nil:NilClass
>> 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での条件式が真のときにだけ実行される式 (後続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の2つしかありません。なお、「!!」(「バンバン (bang bang)」と読みます) という演算子を使うと、そのオブジェクトを2回否定することになるので、どんなオブジェクトも強制的に論理値に変換できます。

>> !!nil
=> false

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

>> !!0
=> true

4.2.4 メソッドの定義

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

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

最後の例を見ると分かるように、メソッドの引数を省略することも可能です (かっこですら省略可能です)。これは、以下のコードでは

def string_message(str = '')

引数にデフォルト値を含めているからです (この例のデフォルト値は空の文字列です)。このように指定すると、str変数に引数を渡すことも渡さないこともできます。引数を渡さない場合は、指定のデフォルト値が自動的に使用されます。

ここで、Rubyの関数には「暗黙の戻り値がある」ことにご注意ください。これは、関数内で最後に評価された式の値が自動的に返されることを意味します (訳注: 関数で戻り値を明示的に指定しなかった場合の動作です)。この場合、引数のstrが空かどうかに応じて、2つのメッセージ文字列のうちのいずれかを返します。もちろん、Rubyでは戻り値を明示的に指定することもできます。以下の関数は上の関数と同じ結果を返します。

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

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

メソッドで引数の変数名にどんな名前を使っても、メソッドの呼び出し側には何の影響も生じないという点にもご注目ください。つまり、最初の例のstrを別の変数名 (the_function_argumentなど) に変更しても、メソッドの呼び出し方は全く同じです。

>> def string_message(the_function_argument = '')
>>   if the_function_argument.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.

4.2.5 title ヘルパー、再び

これで、full_titleヘルパー (リスト4.2) のコードを理解するための準備が整いました5。コメントを使って、各行の振る舞いに注釈を加えてみました (リスト4.9)。

リスト4.9: 注釈付きのtitle_helper. 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
      page_title + " | " + base_title                 # 文字列の結合
    end
  end
end

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

4.3 他のデータ構造

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

4.3.1 配列と範囲演算子

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

Rubyの文字列の理解にだいぶ時間を使ってしまいましたので、次に進むことにします。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]               # Rubyでは角かっこで配列にアクセスする
=> 42
>> a[1]
=> 8
>> a[2]
=> 17
>> a[-1]              # 配列の添字はマイナスにもなれる!
=> 17

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

>> 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.empty?
=> false
>> a.include?(42)
=> true
>> a.sort
=> [8, 17, 42]
>> a.reverse
=> [17, 8, 42]
>> a.shuffle
=> [17, 42, 8]
>> a
=> [42, 8, 17]

上のどのメソッドを実行した場合にも、a自身は変更されていないという点にご注目ください。配列の内容を変更したい場合は、そのメソッドに対応する「破壊的」メソッドを使用します。破壊的メソッドの名前には、元のメソッドの末尾に「!」を追加したものを使用するのがRubyの慣習です。

>> 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]                         # 添字に-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チュートリアルではRuby共通の慣習に従って、短い1行のブロックには波かっこを使用し、長い1行や複数行のブロックにはdo..end記法を使用しています。

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

今度はiの代わりにnumberを使用していることにご注目ください。この変数 (ブロック変数) の名前は固定されていません。

ブロックは見た目よりもずっと奥が深く、ブロックを十分に理解するためには相当なプログラミング経験が必要です。そのためには、ブロックを含むコードをたくさん読みこなすことでブロックの本質を会得する以外に方法はありません8。幸い、人間には個別の事例を一般化する能力というものがあります。ささやかですが、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メソッドは、与えられたブロックを配列や範囲オブジェクトの各要素に対して適用し、その結果を返します。また、後半の2つの例では、mapのブロック内で宣言した引数 (char) に対してメソッドを呼び出しています。こういったケースでは省略記法が一般的で、以下のように書くこともできます。

>> %w[A B C].map { |char| char.downcase }
=> ["a", "b", "c"]
>> %w[A B C].map(&:downcase)
=> ["a", "b", "c"]

(メソッド名にシンボルが使われているので奇妙に見えるかもしれません。これについては4.3.3で説明します)。ひとつ面白い話があります。これは実は元々Ruby on Rails独自の記法でした。しかし多くの人がこの記法を好むようになったので、今ではRubyのコア機能として導入されています。

最後のブロックの例として、単体テストにも目を向けてみましょう (リスト4.4)。

test "should get home" do
  get :home
  assert_response :success
  assert_select "title", "Ruby on Rails Tutorial Sample App"
end

ここでは動作をすみずみまで理解する必要はありません (実際、筆者もこのコードをひと目で完璧に把握できるなどとは言いません)。ここで重要なのは、テストコードにdoというキーワードがあることに気付き、そこからテストの本体が「そもそもブロックでできている」ことに気付くことです。すなわち、このtestメソッドは文字列 (説明文) とブロックを引数にとり、テストが実行されるときにブロック内の文が実行される、ということが理解できます。

ところで、1.5.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]       # 配列の冒頭7つの要素を取り出す
=> ["f", "w", "i", "a", "h", "p", "c", "x"]
>> ('a'..'z').to_a.shuffle[0..7].join  # 取り出した要素を結合してひとつの文字列にする
=> "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"}

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

角かっこを使ってハッシュの要素をひとつづつ定義する代わりに、以下のようにキーと値を=>でリテラル表現するほうが簡単です (この記号はハッシュロケットと呼ばれます)。

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

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

ここまではハッシュのキーに文字列を使用していましたが、Railsのハッシュキーでは文字列よりもシンボルの方が広く使用されています。シンボルは文字列と似ていますが、クォートで囲む代わりにコロンが前に置かれている点が異なります。たとえば、:nameはシンボルです。もちろん、シンボルを「余分なものを削ぎ落した軽量な文字列」とみなしても構いません10

>> "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ではシンボルをふんだんに使用しているので、すぐに慣れるでしょう。ただし文字列と違って、利用できない記号がある点にご注意ください

>> :foo-bar
NameError: undefined local variable or method `bar' for main:Object
>> :2foo
SyntaxError

一般的な英文字を使っている限り、シンボルの名前で不自由することはないでしょう。

ハッシュのキーとしてシンボルを採用する場合、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コミュニティでも人気が高まっています。どちらの記法もよく使われているので、両方の見分けがつくことが重要です。ただ最初は少し見分けづらいのも事実です。たとえば:nameは単独でシンボルとして使えますが、name:はその後ろに引数がなければ意味がありません。以下のコードの:name =>name:は、ハッシュとしてのデータ構造は全く同じです。つまり、

{ :name => "Michael Hartl" }

{ name: "Michael Hartl" }

というコードは等価になります (一般的には省略記法が好まれますが、明示的に接頭にコロンをつけてシンボル (:name) であることを強調するという考え方もあります)。

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

リスト4.10: ハッシュの中のハッシュ
>> 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:dangerという 2つの状態を持つ flash という名前のハッシュについて考えてみましょう。

>> flash = { success: "It worked!", danger: "It failed." }
=> {:success=>"It worked!", :danger=>"It failed."}
>> flash.each do |key, value|
?>   puts "Key #{key.inspect} has value #{value.inspect}"
>> end
Key :success has value "It worked!"
Key :danger 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を使用することは非常によくあることなので、 p関数というショートカットがあります11

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

ハイフン (-) が入っているためにエラーが発生してしまいます。(4.3.3で、シンボルにハイフンが使えなかったことを思い出してください) このため、以下のような旧式のハッシュロケット記法を使用するしかないのです。

'data-turbolinks-track' => true

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

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

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

従って、

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

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

リスト 4.11: インクルードされた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メソッドを呼び出します14

>> 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
=> :palindrome?

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

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

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

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

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

>> s = Word.new("level")    # 新しいWordを作成し、"level" で初期化する
=> "level"
>> s.palindrome?            # Wordが鏡文字かどうかを調べるメソッド
=> 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.12の (組み込みではない) Wordクラスの継承階層。

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

self == self.reverse

単語が回文であるかどうかを確認できるということです15。なお、Stringクラスの内部では、メソッドや属性を呼び出すときのself.も省略可能です。

self == reverse

といった省略記法でも、うまく動きます。

4.4.3 組込みクラスの変更

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

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

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

>> 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.4で説明します。

4.4.4 コントローラクラス

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

class StaticPagesController < ApplicationController

  def home
  end

  def help
  end

  def about
  end
end

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

>> 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とは切り離して学習する必要があります。

4.4.5 ユーザークラス

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

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

リスト4.13: 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.23.6でも説明したように、@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 のコードを読み込む方法
=> true
>> example = User.new
=> #<User:0x224ceec @email=nil, @name=nil>
>> example.name                 # attribuributes[:name]は存在しないのでnil
=> nil
>> example.name = "Example User"           # 名前を代入する
=> "Example User"
>> example.email = "user@example.com"      # メールアドレスを代入する
=> "user@example.com"
>> example.formatted_email
=> "Example User <user@example.com>"

上のコードで、requireのパスにある’.’は、Unixの “カレントディレクトリ” (現在のディレクトリ) を表し、’./example_user’というパスは、カレントディレクトリからの相対パスでexample_userファイルを探すようにRubyに指示します。次のコードでは空のexample_userを作成します。次に、対応する属性にそれぞれ手動で値を代入することで、名前とメールアドレスを与えます (リスト4.13attr_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

その他の変更はリポジトリにコミットしましょう。その後、Bitbucketにプッシュし、Herokuにデプロイしましょう。

$ git status
$ git commit -am "Add a full_title helper"
$ git push
$ bundle exec rake test
$ git push heroku

4.5.1 本章のまとめ

  • Rubyは文字列を扱うためのメソッドを多数持っている
  • Rubyの世界では、すべてがオブジェクトである
  • Rubyではdefというキーワードを使ってメソッドを定義する
  • Rubyではclassというキーワードを使ってクラスを定義する
  • Railsのビューでは静的なHTMLと埋め込みRuby(ERb)が使える
  • Rubyの組み込みクラスには配列、範囲、ハッシュなどがある
  • Rubyのブロックは (他の似た機能と比べ) 柔軟な機能で、添え字を使ったデータ構造よりも自然にイテレーションができる
  • シンボルとはラベルである。追加的な構造を持たない (代入などができない) 文字列みたいなもの。
  • Rubyではオブジェクトを継承できる
  • Rubyでは組み込みクラスですら内部を見たり修正したりできる
  • 「“deified”」という単語は回文である

4.6 演習

: 『演習の解答マニュアル (英語)』にはRuby on Railsチュートリアルブックのすべての演習の解答が掲載されており、www.railstutorial.orgでチュートリアルを購入いただいた方には無料で付属します。

  1. リスト4.14のコードにあるすべての疑問符を、それぞれ適切なメソッドに置き換えて、与えられた文字列の文字をシャッフルする関数を作成してください。ヒント: splitメソッド、shuffleメソッド、joinメソッドを組み合わせてみましょう。
  2. リスト4.15を参考にして、上で作成したshuffleメソッドを Stringクラスに追加してください。
  3. person1person2person3という3つのハッシュを作成してください。それぞれのハッシュには:firstキーと:lastキーを与え、さらにそれぞれのキーに名前と名字を値として割り当ててください。次にparamsハッシュを作成し、params[:father]person1params[:mother]person2、そしてparams[:child]person3になるようにしてください。最後に、params[:father][:first]などが正しい値を持っていることを確認してください。
  4. Ruby API のオンラインマニュアルを見つけて、Hashクラスのmergeメソッドについて読んでみてください。では、次の式の値は何ですか?
      { "a" => 100, "b" => 200 }.merge({ "b" => 300 })
    
リスト 4.14: 文字列をシャッフルする関数の骨組み。
>> def string_shuffle(s)
>>   s.?('').?.?
>> end
>> string_shuffle("foobar")
=> "oobfra"
リスト 4.15: shuffleメソッドをStringクラスに追加するための骨組み。
>> class String
>>   def shuffle
>>     self.?('').?.?
>>   end
>> end
>> "foobar".shuffle
=> "borafo"
  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ヘルパーをビューで利用できるようにする方法などがそうです。(この点を深く理解したい方には、「The Rails 4 Way」(Obie Fernandez著) がお勧めです) 。
  6. 文字列の式展開は魅力的です。実際、以前のチュートリアルではこれを多用していました。しかし、 provideメソッドは内部で文字列オブジェクトをSafeBufferオブジェクトに変換してしまうので、注意が必要です。文字列オブジェクトではないため、式展開をビューの中で使うと多くのエスケープ処理が実行されます。たとえば「Help’s on the way」は「Help&amp;#39;s on the way」といった具合にエスケープされます (この問題を指摘いただいたJeremy Fleischmanに感謝します)。
  7. このコードで使用しているsecondメソッドは、実はRuby自身の一部ではなく、Railsが追加したものです。このコードが動作するのは、RailsによるRubyの拡張がRailsコンソールによって自動的に反映されるからです。
  8. なおエキスパート向けには、ブロックがクロージャになっているということを知っていただくと理解しやすいと思います。クロージャとは、データを伴う、その場限りの無名関数です。
  9. 実はRuby 1.9以降では、ハッシュの要素の順序が入力順と同じであることを保証していますが、ハッシュを特定の順序に依存してカウントするのは得策ではありません。
  10. シンボルは文字列に比べてはるかに軽量なので、シンボル同士の比較を高速に行えます。文字列の比較は1文字ずつ行われる必要がありますが、シンボルでは瞬時に全体を比較できます (訳注: シンボルは内部的にはむしろ「固定桁の数値」であり、異なるシンボル同士の数値は重複しないことが保証されます)。これはハッシュのキーとして理想的な性質です。
  11. 実際には些細な違いがあり、pメソッドは画面出力だけでなく返り値もオブジェクトになります。しかし、putsメソッドの場合は引数によらず必ずnilが返り値になります。(指摘してくれたKatarzyna Siwekに感謝します。)
  12. 改行は、行の末尾と次の行の始まりを示します。コードの中では、\nの文字列で表します。
  13. もちろん、人間がいちいち文字数を数えていたら頭がどうにかなってしまいます。だからこそ、多くのテキストエディタにはこれらを支援する機能が備わっています。たとえば、図1.5をもう一度見てみると、コードを80文字以下に抑えるための小さな縦線が右側に見えます。1.2.1で紹介したCloud IDEでは、デフォルトでこのような行が含まれます。TextMateを使用していれば、View > Wrap Column > 78で設定できます。Sublime Textを使用していれば、View > Ruler > 78、またはView > Ruler > 80で設定できます。
  14. このメソッドの動作は、使用しているRubyのバージョンによって異なる可能性があります。この例ではRuby 1.9.3以上のバージョンを前提としています。
  15. Rubyのクラスやselfについてもっと詳しく知りたい場合は、RailsTipsに投稿された “Rubyにおけるクラスとインスタンス変数” (英語) を参照してください。
  16. これらの階層にあるクラスの詳細を知る必要はないと思います。私ですらそれらのクラスの詳細について知らないことがたくさんありますし、それでも私は2005年からRuby on Railsで問題なくプログラミングできています。これは (a) 私がよほど無能であるか、(b) Railsの内部を知りつくさなくても熟練したRails開発者になれる、ということのどちらかでしょう。私のためにも読者の皆様のためにも、後者であることを祈ります。

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

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

本章では、サンプルアプリケーションにレイアウトを追加したり、修正したりといった部分に注力していきます。また、レイアウトについてはテスト駆動開発で進めたり、全くテストを書かない箇所もでてきます。テストを書くときと書かないときのガイドラインについては、コラム3.3で解説します。この結果、本章ではテキストエディタによる修正とブラウザによる確認がほとんどになります。テスト駆動開発で進める唯一の箇所は、5.3.1のContactページの追加する箇所のみです。最後に、新しいテスト手法「 統合テスト (Integration Test)」について紹介します (5.3.4)。統合テストを使って、最終的なレイアウトやリンクが正しいかどうかをチェックします。

5.1 構造を追加する

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

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

images/figures/home_page_mockup_3rd_edition
図5.1 サンプルアプリケーションのHomeページのモックアップ

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

$ git checkout master
$ 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="//cdnjs.cloudflare.com/ajax/libs/html5shiv/r29/html5.min.js">
      </script>
    <![endif]-->
  </head>
  <body>
    <header class="navbar navbar-fixed-top navbar-inverse">
      <div class="container">
        <%= link_to "sample app", '#', id: "logo" %>
        <nav>
          <ul class="nav navbar-nav navbar-right">
            <li><%= link_to "Home",   '#' %></li>
            <li><%= link_to "Help",   '#' %></li>
            <li><%= link_to "Log in", '#' %></li>
          </ul>
        </nav>
      </div>
    </header>
    <div class="container">
      <%= yield %>
    </div>
  </body>
</html>

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

<!--[if lt IE 9]>
  <script src="//cdnjs.cloudflare.com/ajax/libs/html5shiv/r29/html5.min.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="container">
    <%= link_to "sample app", '#', id: "logo" %>
    <nav>
      <ul class="nav navbar-nav navbar-right">
        <li><%= link_to "Home",   '#' %></li>
        <li><%= link_to "Help",   '#' %></li>
        <li><%= link_to "Log in", '#' %></li>
      </ul>
    </nav>
  </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="container">

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

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

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

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

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

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

正確にはここでは不要なのですが、navタグには「その内側がナビゲーションリンクである」という意図を明示的に伝える役割があります。さらに、ulタグに付与されているnavnavbar-navnavbar-rightクラスもBootstrapにおいて特別な意味を持ちます。したがって、5.1.2でBootstrapのCSSを追加したときに、これらのスタイルも自動的に適用されます。ブラウザからソースを見ることで確認ができますが、Railsが埋め込みRubyを評価し、レイアウトを描画すると、上のリストは以下のように置き換わります5

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

これがブラウザに返されるHTMLになります。

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

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

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

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

リスト5.2: サインアップページへのリンクがあるHomeページ app/views/static_pages/home.html.erb
<div class="center jumbotron">
  <h1>Welcome to the Sample App</h1>

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

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

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

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

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

上で挙げたdivタグのCSSクラスjumbotronや、signupボタンのbtnクラス、btn-lgクラス、btn-primaryクラスはすべて、Bootstrapにおいて特別な意味を持ちます。

2番目のlink_toでは、引数として画像ファイルのパスと任意のオプションハッシュをとるimage_tagヘルパーの能力が示されています。シンボルを使用して、この場合はalt属性を設定しています。画像を表示するためには、rails.pngというRailsのロゴ画像ファイルを加える必要があります。Ruby on Rails公式ページの http://railstutorial.jp/rails.png から画像をダウンロードして、app/assets/images/ディレクトリに置いてください。Cloud IDEやUnix系のOS (Max OS Xなど) を使っている場合は、次のようにcurlコマンドで簡単に取得できます6

$ curl -OL http://railstutorial.jp/rails.png
$ mv rails.png app/assets/images/

Cloud IDEを使っていると、(筆者にも理由は分からないのですが) ときどき2行目のmvコマンドで失敗することがあるようです。その場合は、1行目のcurlコマンドをもう一度実行して、ロゴ画像が正しくダウンロードできているかどうか確認してください (curlコマンドの詳細については、Conquering the Command Lineという本の第3章 (英語) を参照してください) 。リスト5.2image_tagヘルパーを使っているので、Railsは該当する画像ファイルを、アセットパイプラインを通してapp/assets/images/ディレクトリの中から探してくれます (アセットパイプラインについては5.2で説明します)。

image_tagの効果を確かめるために、ブラウザから生成されたHTMLを見てみましょう7

<img alt="Rails logo" src="/assets/rails-9308b8f92fea4c19a3a0d8385b494526.png" />

ファイル名が重ならないようにするために、Railsが9308b8f92fea4c19a3a0d8385b494526という文字列 (実際の文字列はシステムごとに異なります) を追加していることがわかります。これは、たとえば画像ファイルを新しい画像に更新したときに、ブラウザ内に保存されたキャッシュに意図的にヒットさせないようにするための仕組みです。また、src属性には "images" というディレクトリ名が含まれていないことにも注目してください。これはassetsディレクトリ内の他のディレクトリ (imagesやjavascripts、stylesheetsなど) も同様です。これは高速化のための仕組みで、Railsはassetsディレクトリ直下の画像をapp/assets/imagesディレクトリにある画像と紐付けています。これにより、ブラウザから見るとすべてのファイルが同じディレクトリにあるように見えるようになります。そして、このようなフラットなディレクトリ構成を採っていると、ファイルをより高速にブラウザに渡すことができるようになります。最後に、alt属性は、画像がない場合に代わりに表示される文字列です。たとえば視覚障害のあるユーザーが使用するスクリーンリーダーでは、ここの属性が読み上げられて、そこに画像があることが示されます。

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

images/figures/layout_no_logo_or_custom_css_bootstrap_3rd_edition
図5.2 カスタムCSSを使用していないHomeページ

5.1.2 BootstrapとカスタムCSS

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

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

リスト5.3: Gemfilebootstrap-sassを追加する
source 'https://rubygems.org'

gem 'rails',                '4.2.2'
gem 'bootstrap-sass',       '3.2.0.0'
.
.
.

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

$ bundle install

ちなみに、rails generateコマンドを実行することでコントローラーごとに分けられたCSSファイルが自動的に生成されますが、これらのファイルを正しい順序で読み込ませるのは至難の技なので、本チュートリアルでは (簡潔のために) すべてのCSSを1つにまとめる方針を採っています。カスタムCSSを動かすための最初の一歩は、カスタムCSSファイルを作ることです。

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

(ここでは3.3.3の途中で紹介したtouchコマンドを使っていますが、ファイルが作成できるなら [新規ファイル作成] や他のコマンドでも問題ありません。) このディレクトリ名とファイル名は、どちらも重要です。以下のディレクトリは、

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.4のように@importを使用して、Bootstrap (とそれに関連するSprockets) をインクルードします9

リスト5.4: Bootstrap CSSを追加する app/assets/stylesheets/custom.css.scss
@import "bootstrap-sprockets";
@import "bootstrap";

リスト5.4の2行では、Bootstrap CSSのフレームワークを導入しています。導入後、Webサーバを再起動させると、アプリケーションに反映させることができます (1.3.2で紹介したように、Ctrl-Cを押してWebサーバを停止させた後、rails serverコマンドを打ってWebサーバを起動してください)。うまくいけば5.3のような結果になります。さて、テキストの配置は今ひとつで、ロゴにはスタイルもありませんが、色使いとsignupボタンはなかなかよい感じになってきました。

images/figures/sample_app_only_bootstrap_3rd_edition
図5.3 Bootstrap CSSとサンプルアプリケーション

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

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

/* universal */

body {
  padding-top: 60px;
}

section {
  overflow: auto;
}

textarea {
  resize: vertical;
}

.center {
  text-align: center;
}

.center h1 {
  margin-bottom: 10px;
}
images/figures/sample_app_universal_3rd_edition
図5.4 スペースや共通スタイルを追加した結果

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

body {
  padding-top: 60px;
}

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

.center {
  text-align: center;
}

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

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

リスト5.6: 洗練されたタイポグラフィーを利用するためのCSSを追加する app/assets/stylesheets/custom.css.scss
@import "bootstrap-sprockets";
@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: #777;
}

p {
  font-size: 1.1em;
  line-height: 1.7em;
}
images/figures/sample_app_typography_3rd_edition
図5.5: タイポグラフィースタイルを追加する

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

リスト5.7: サイトロゴにCSSを追加する app/assets/stylesheets/custom.css.scss
@import "bootstrap-sprockets";
@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;
}

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

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

5.1.3 パーシャル (partial)

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

リスト5.8: レイアウトにshimとheaderのパーシャルを追加する 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.8では、以下のようにrenderと呼ばれるRailsヘルパー呼び出しだけを使って、HTML shimのスタイルシート行を置換しています。

<%= render 'layouts/shim' %>

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

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

リスト5.9: HTML shim用のパーシャル app/views/layouts/_shim.html.erb
<!--[if lt IE 9]>
  <script src="//cdnjs.cloudflare.com/ajax/libs/html5shiv/r29/html5.min.js">
  </script>
<![endif]-->

同様に、他のヘッダーの情報もリスト5.10のパーシャルに移動し、renderを呼び出してレイアウトに挿入することができます。(パーシャルでは、自動生成せずに、テキストエディタを使って手動で作成するのが一般的です。)

リスト5.10: header用のパーシャル app/views/layouts/_header.html.erb
<header class="navbar navbar-fixed-top navbar-inverse">
  <div class="container">
    <%= link_to "sample app", '#', id: "logo" %>
    <nav>
      <ul class="nav navbar-nav navbar-right">
        <li><%= link_to "Home",   '#' %></li>
        <li><%= link_to "Help",   '#' %></li>
        <li><%= link_to "Log in", '#' %></li>
      </ul>
    </nav>
  </div>
</header>

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

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

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

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

5.2 SassとAsset Pipeline

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

5.2.1 Asset Pipeline

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

アセットディレクトリ

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

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

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

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

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

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

$ ls app/assets/
images/  javascripts/  stylesheets/

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

マニフェストファイル

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

リスト5.14: アプリケーション固有のCSS用マニフェストファイル app/assets/stylesheets/application.css
/*
 * This is a manifest file that'll be compiled into application.css, which
 * will include all the files listed below.
 *
 * Any CSS and SCSS file within this directory, lib/assets/stylesheets,
 * vendor/assets/stylesheets, or vendor/assets/stylesheets of plugins, if any,
 * can be referenced here using a relative path.
 *
 * You're free to add application-wide styles to this file and they'll appear
 * at the bottom of the compiled file so the styles you add here take
 * precedence over styles defined in any styles defined in the other CSS/SCSS
 * files in this directory. It is generally better to create a new file per
 * style scope.
 *
 *= require_tree .
 *= require_self
 */

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

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

以下の行は、

*= 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.4.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を使うと、この「開発効率と読み込み時間のどちらを重視するか」という問題について悩む必要がなくなります。開発環境ではプログラマにとって読みやすいように整理しておき、本番環境ではAsset Pipelineを使ってファイルを最小化すればよいのです。具体的には、Asset Pipelineがすべてのスタイルシートを1つのCSSファイル (application.css) にまとめ、すべてのJavaScriptファイルを1つのJSファイル (javascripts.js) にまとめてくれます。さらに、それらのファイルすべてに対して 不要な空白やインデントを取り除く処理を行い、ファイルサイズを最小化してくれます。結果として、開発環境と本番環境という、2つの異なった状況に対してそれぞれ最高の環境を提供してくれます。

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

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

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

ネスト

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

.center {
  text-align: center;
}

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

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

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

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

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

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

#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;
  &:hover {
    color: #fff;
    text-decoration: none;
  }
}

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

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

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

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

変数

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

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

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

$light-gray: #777;

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

$light-gray: #777;
.
.
.
h2 {
  .
  .
  .
  color: $light-gray;
}
.
.
.
footer {
  .
  .
  .
  color: $light-gray;
}

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

 @gray-light: #777;

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

h2 {
  .
  .
  .
  color: $gray-light;
}
.
.
.
footer {
  .
  .
  .
  color: $gray-light;
}

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

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

/* mixins, variables, etc. */

$gray-medium-light: #eaeaea;

/* universal */

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: $gray-light;
}

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;
  &:hover {
    color: white;
    text-decoration: none;
  }
}

/* footer */

footer {
  margin-top: 45px;
  padding-top: 5px;
  border-top: 1px solid $gray-medium-light;
  color: $gray-light;
  a {
    color: $gray;
    &:hover {
      color: $gray-darker;
    }
  }
  small {
    float: left;
  }
  ul {
    float: right;
    list-style: none;
    li {
      float: left;
      margin-left: 15px;
    }
  }
}

Sassを使ってスタイルシートをより簡単にする方法は他にもありますが、今回はその中でも最も重要な機能を使ってリスト5.16を書き直しました。Sassを使うことによって、素晴らしいスタートを切ることができました。Sassの詳細については、Sass の公式サイト (英語) を参照してください。

5.4 ユーザー登録: 最初のステップ

この節では、レイアウトとルーティングの取り組みにおける頂点として、ユーザー登録ページへのルーティングを作成します。そのために2番目のコントローラを作成することになります。これは、Webサイトでユーザー登録を行えるようにするための最初の重要な一歩となります。次の一歩であるユーザーのモデリングは第6章で行い、第7章でユーザー登録が完成します。

5.4.1 Usersコントローラ

3.2で、最初のコントローラであるStaticPagesコントローラを作成しました。今度は2番目のコントローラであるUsersコントローラを作成しましょう。1番目のときと同様、generateを実行して、現時点での要求である新規ユーザー用のユーザー登録ページ (スタブ) を持つ、最も簡単なコントローラを作成します。Railsで好まれているRESTアーキテクチャの規約に従い、新規ユーザー用のアクションをnewとします。したがって、generate controllerの引数にnewを渡して、自動的にアクションを作成してみましょう。変更の結果をリスト5.28に示します。

リスト5.28: Usersコントローラの生成 (newアクションを追加)
$ rails generate controller Users new
      create  app/controllers/users_controller.rb
       route  get 'users/new'
      invoke  erb
      create    app/views/users
      create    app/views/users/new.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  assets
      invoke    coffee
      create      app/assets/javascripts/users.js.coffee
      invoke    scss
      create      app/assets/stylesheets/users.css.scss

リスト5.28により、newアクションを持つUsersコントローラ(リスト5.30)と、スタブのユーザービューを作成します(リスト5.31)。このとき、新しいUserページ用の小さなテスト (リスト5.32) も生成されていて、この時点ではパスするはずです。

リスト5.29: green
$ bundle exec rake test
リスト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.32: Userページ用の最初のテスト GREEN test/controllers/users_controller_test.rb
require 'test_helper'

class UsersControllerTest < ActionController::TestCase

  test "should get new" do
    get :new
    assert_response :success
  end
end

5.4.2 ユーザー登録用URL

5.4.1のコードにより、新規ユーザー用の動作するページが/users/new にできました。ここで5.1を思い出していただきたいのですが、URLは/users/newではなく表のとおりに/signupにしたいと思います。リスト5.22の例に従い、ユーザー登録URL用にget ’/signup’のルールを追加します (リスト5.33)。

リスト5.33: ユーザー登録ページのルート config/routes.rb
Rails.application.routes.draw do
  root             'static_pages#home'
  get 'help'    => 'static_pages#help'
  get 'about'   => 'static_pages#about'
  get 'contact' => 'static_pages#contact'
  get 'signup'  => 'users#new'
end

次に、新しく定義された名前付きルートを使って、Homeページのボタンに適切なリンクを追加します。他のルートと同様、get ’/signup’と記述したことでsignup_pathという名前付きルートができ、それをリスト5.34で使用します。signupページへのテストは演習に回すことにします (5.6)。

最後に、signupページ用のカスタムスタブ (stub) のビューを追加します (5.35)。

リスト5.35: 最初のユーザー登録ページ (スタブ) app/views/users/new.html.erb
<% provide(:title, 'Sign up') %>
<h1>Sign up</h1>
<p>This will be a signup page for new users.</p>

これで、少なくともサインインのルートを追加するまでの間、リンクと名前付きルートが完成しました(第8章)。結果を5.9の新規ユーザーのページ (URI /signup) に示します。

images/figures/new_signup_page_3rd_edition
図5.9 /signupで表示される新しいユーザー登録ページ

5.5 最後に

この章では、アプリケーションのレイアウトを形にし、ルーティングを洗練させました。本書では、以後サンプルアプリケーションを肉付けすることに専念します。最初に、ユーザー登録、サインイン、サインアウトできるユーザーを追加します。次に、マイクロポストを追加します。最後に、他のユーザーをフォローできるようにします。

Gitを使っている方は、この時点でmasterブランチに変更をマージしてください。

$ bundle exec rake test
$ git add -A
$ git commit -m "Finish layout and routes"
$ git checkout master
$ git merge filling-in-layout

続いて、Bitbucketにプッシュします。

$ git push

最後に、Herokuへデプロイします。

$ git push heroku

デプロイが無事に終わると、本番環境でサンプルアプリケーションが動いているはずです (5.10)。

images/figures/layout_production
図5.10: 本番環境で動くサンプルアプリケーション

5.5.1 本章のまとめ

  • HTML5を使ってheaderやfooter、logoやbodyといったコンテンツのレイアウトを定義しました
  • Railsのパーシャルは効率化のために使われ、別ファイルにマークアップを切り出すことができます
  • CSSは、CSSクラスとidを使ってレイアウトやデザインを調整します
  • Bootstrapフレームワークを使うと、いい感じのデザインを素早く実装できる
  • SassとAsset Pipelineは、(開発効率のために切り分けられた) CSSの冗長な部分を圧縮し、本番環境に最適化した結果を出力する
  • Railsのルーティングでは自由にルールを定義することができ、また、その際に名前付きルートも使えるようになる
  • 統合テストは、ブラウザによるページ間の遷移を効率的にシミュレートする

5.6 演習

: 『演習の解答マニュアル (英語)』にはRuby on Railsチュートリアルのすべての演習の解答が掲載されており、www.railstutorial.orgから購入して頂いた方は無料で手に入れることができます。

演習とチュートリアル本編の食い違いを避ける方法については、3.6のトピックブランチの演習に追加したメモをご覧ください。

  1. 5.2.2で触れたように、まずはリスト5.13のフッター用CSSをリスト5.15のSCSSに変更してみてください。
  2. リスト5.25の統合テストに、 getメソッドを使ってユーザー登録ページにアクセスし、ページタイトルが正しいかどうかチェックするテストコードを加えてください。
  3. リスト5.36で示すように、Applicationヘルパーで使っているfull_titleヘルパーを、test環境でも使えるようにすると便利です。こうしておくと、リスト5.37のようなコードを使って正しいタイトルをテストすることができます (ちなみにこれは前回の演習の応用でもあります)。ただし、これは完璧なテストではありません。たとえばベースタイトルに“Ruby on Rails Tutoial”といった誤字があったとしても、このテストでは発見することができないでしょう。この問題を解決するためには、full_titleヘルパーに対するテストを書く必要があります。そのために、Applicationヘルパーをテストするファイルを作成し、リスト5.38FILL_INの部分を適切なコードに置き換えてみてください。(ヒント: リスト5.38ではassert_equal <期待される値>, <実際の値>といった形で使っていましたが、内部では==演算子を使って、期待される値と実際の値を比較して正しいかどうかテストしています。)
リスト5.36: テスト環境でもApplicationヘルパーを使えるようにする test/test_helper.rb
ENV['RAILS_ENV'] ||= 'test'
.
.
.
class ActiveSupport::TestCase
  fixtures :all
  include ApplicationHelper
  .
  .
  .
end
リスト5.38: full_titleヘルパーの単体テスト test/helpers/application_helper_test.rb
require 'test_helper'

class ApplicationHelperTest < ActionView::TestCase
  test "full title helper" do
    assert_equal full_title,         FILL_IN
    assert_equal full_title("Help"), FILL_IN
  end
end
  1. Colm Tuiteの多大な貢献により、サンプルアプリケーションをBootstrap CSSのフレームワークに変換することができました。感謝します。
  2. Ruby on Railsチュートリアル のモックアップは、「Mockingbird」という素晴らしいモックアップ作成サービスで作られています。
  3. shimshivという単語は、今回の用途ではどちらでも大丈夫です。shimを意味は「洗う機械、もしくは薄い物質を整理しフィットさせるためのモノ、あるいは服を削除すること」なので、意味合いとしては前者が正しいです。ちなみに後者は「ナイフ、もしくは武器として使う剃刀」という意味なので、おそらく原著者である Sjoerd Visscherのちょっとしたイタズラ心でしょう。
  4. CSSクラスは、Rubyのクラスとはまったく関係がありません。
  5. スペースを入れると見栄えが変わるかもしれませんが、3.4.1で触れたようにHTMLは重複する空白を無視するのでどちらでも大丈夫です。
  6. もしOS XのHomebrewが使えるようになっていれば、brew install curlというコマンドを打ってcurlをインストールすることができます。 
  7. 既にお気付きだと思いますが、imgタグは<img>...</img>ではなく<img ... />と書きます。この書式に従うタグは閉じタグとして知られています。 
  8. Asset PipelineではLessを使うこともできます。詳しくはless-rails-bootstrap gemを参照してください。
  9. もしこのステップが摩訶不思議に思えたら、次のように考えてみましょう。「私はただbootstrap-sassのREADMEファイルに従っているのだ」と。
  10. 多くのRails開発者は、異なるビューの間で共通に使用するパーシャルを保存するディレクトリとして、sharedディレクトリを使用します。著者は、複数のビューで共有するユーティリティパーシャルについてはsharedフォルダに保存し、文字どおり全ページ (サイトレイアウトの一部として) 共通のパーシャルについてはlayoutsディレクトリへ保存することを好んでいます (sharedディレクトリは第7章で作成します)。著者はこのように分割保存するのが論理的であると考えますが、sharedフォルダにすべて保存しても問題なく動作します。
  11. footerタグと.footerクラスを両方使用していることについて疑問に思う方がいるかもしれません。その理由は、footerタグとする方が読み手にとって意味が明確であるのと、.footerクラスはBootstrapで使用するためです。footerdivに置き換えても動作は変わりません。
  12. このチュートリアル構成は、Michael Erasmusによる素晴らしいブログ記事「5分でわかるRails 3のAsset Pipeline (英語)」をもとにしています。詳細についてはRailsガイドの「アセットパイプライン」の項を参照してください。
  13. Sassでもサポートされている古い.sassというフォーマットでは、冗長性の少ない (括弧の少ない) 新しい言語を定義しますが、既存のプロジェクトには若干不便であり、既にCSSに慣れ親しんだ人にとっては学習が面倒でもあります。
  14. 何人かの開発者は「1つのテストに複数のアサーションを入れるべきではない」と強く主張するでしょう。 この演習は不必要に複雑で、もし各テストの直前に共通のセットアップ用タスクがあれば、たしかに不要な負荷がかかることでしょう。しかし、よく書かれたテストは一貫したストーリーのようになり、人間にとって理解しやすいです。ストーリーを独立した場面ごとに分割されてしまうと、物語調ではなくなってしまいます。このことから、複数のアサーションを1つのテストにまとめるようにして、(minitestを通して) Rubyにどのセリフで間違ったのかを話させるようにしています。

第6章 ユーザーのモデルを作成する

第5章では、新しいユーザーを作成するためのスタブページを作ったところで終わりました (5.4)。これから5つの章を通して、ユーザー登録ページを作っていくことにしましょう。本章では、一番重要なステップであるユーザー用のデータモデルの作成と、データを保存する手段の確保について学んでいきます。第7章では、ユーザーがサイトにユーザー登録できるようにし、ユーザープロファイルのためのページを作成します。ユーザー登録できるようになったら、ログインやログアウトをできる仕組みを作り (第8章)、第9章からは不正なアクセスを取り扱う方法について学んでいきます (9.2.1) 。最後に、第10章でメールアドレスを使ってアカウントを有効化する方法と、パスワードをリセットする方法について学びます。まとめると、第6章から第10章を通して、Railsのログインと認証システムをひととおり開発します。ご存知の方もいると思いますが、Railsでは既にさまざまな認証方法が利用可能です。コラム6.1では、最初に少なくとも一度は自分で認証システムを作ってみることをお勧めする理由について説明しています。

コラム6.1 自分で認証システムを作ってみる

事実上、すべてのWebアプリケーションは何らかのログイン/認証システムを必要とします。そのため、多くのWebフレームワークではこのようなログイン/認証システムを実装するための選択肢が多数提供されています。Railsもまた例外ではありません。認証 (authentication) と認可 (authorization) のシステムの例だと、ClearanceAuthlogicDeviseCanCanなどがあります (Railsに限らなければOpenIDOAuthの上に構築する方法もあります)。なぜ車輪の再発明をするのか、という質問があるのも当然です。自分でわざわざ作らなくても、いつも使える方法をただ利用するだけではいけないのでしょうか。

ある実践的な実験によると、多くのサイトの認証システムは膨大なカスタマイズを必要とするため、サードパーティ製品を変更して導入する場合にはシステムをゼロから作成するよりも多くの仕事を要するという結果が出ています。加えて、既成品のシステムは内部がわかりづらいことが多く、ブラックボックスになっています。自分で作成したシステムであれば、それをとてもよく理解しているはずです。さらに言えば、最近のRailsへの変更 (6.3) により、カスタム認証システムを容易に作成できるようになりました。最後に、あえて最終的にサードパーティの認証システムを導入することになったとしても、自分自身で認証システムを構築した経験があれば、サードパーティ製品を理解して変更することがずっと容易になるはずです。

6.1 Userモデル

ここから3つの章にわたる最終目標はユーザー登録ページ (6.1のモックアップ) を作成することですが、今のままでは新しいユーザーの情報を受け取っても保存する場所がないので、いきなりページを作成するわけにはいきません。ユーザー登録でまず初めにやることは、それらの情報を保存するためのデータ構造を作成することです。

images/figures/signup_mockup_bootstrap
図7.1 ユーザー登録ページのモックアップ

Railsでは、データモデルで使用するデフォルトのデータ構造のことをモデルと呼びます (1.3.3で言うMVCのMのことです)。Railsでは、データを永続化するデフォルトの解決策として、データベースを使用してデータを長期間保存します。また、データベースとやりとりするデフォルトのRailsライブラリはActive Recordと呼ばれます1。Active Recordは、データオブジェクトの作成/保存/検索のためのメソッドを持っています。これらのメソッドを使用するのに、リレーショナルデータベースで使うSQL (Structured Query Language)2を意識する必要はありません。さらに、Railsにはマイグレーションという機能があります。データの定義をRubyで記述することができ、SQLのDDL (Data Definition Language)を新たに学ぶ必要がありません。Railsは、データストアの詳細からほぼ完全に私たちを切り離してくれます。本書では、SQLiteを開発 (development) 環境で使い、またPostgreSQLを (Herokuでの) 本番環境で使います (1.5)。Railsは、本番 (production) アプリケーションですら、データの保存方法の詳細についてほとんど考える必要がないくらいよくできています。

Gitでバージョン管理を行なっているのであれば、このタイミングでユーザーをモデリングするためのトピックブランチを作成しておいてください。

$ git checkout master
$ git checkout -b modeling-users

6.1.1 データベースの移行

4.4.5で扱ったカスタムビルドクラスのUserを思い出してください。このクラスは、nameemailを属性に持つユーザーオブジェクトでした。このクラスは役に立つ例として提供されましたが、Railsにとって極めて重要な部分である永続性という要素が欠けていました。RailsコンソールでUserクラスのオブジェクトを作っても、コンソールからexitするとそのオブジェクトはすぐに消えてしまいました。この節での目的は、簡単に消えることのないユーザーのモデルを構築することです。

4.4.5のユーザークラスと同様に、nameemailの2つの属性からなるユーザーをモデリングするところから始めましょう。後者のemailを一意のユーザー名として使用します3 (パスワードのための属性は6.3で扱います)。リスト4.13では、以下のようにRubyのattr_accessorメソッドを使用しました。

class User
  attr_accessor :name, :email
  .
  .
  .
end

それとは対照的に、Railsでユーザーをモデリングするときは、属性を明示的に識別する必要がありません。上で簡潔に述べたように、Railsはデータを保存する際にデフォルトでリレーショナルデータベースを使用します。リレーショナルデータベースは、データで構成されるテーブルからなり、各行はデータ属性のカラム (列) を持ちます。たとえば、nameとemailを持つユーザーを保存するのであれば、nameemailのカラムを持つusersテーブルを作成します (各行は1人のユーザーを表します)。テーブルに格納されるデータの例を6.2に、対応するデータモデルを6.3に示します (なお、6.3は草案です。実際のデータモデルは6.4のようになります)。nameemailといったカラム名を今のうちに考えておくことで、後ほどUserオブジェクトの各属性をActiveRecordに伝えるときに楽になります。

images/figures/users_table
図6.2: usersテーブルに含まれるデータのサンプル
images/figures/user_model_sketch
図6.3: Userのデータモデルのスケッチ

リスト5.28で、ユーザーコントローラ (とnewアクション) を作ったときに使った以下のコマンドを思い出してみてください。

$ rails generate controller Users new

モデルを作成するときは、上と似たようなパターンでgenerate modelというコマンドを使います。さらに、今回はnameemailといった属性を付けたUserモデルを使いたいので、実際に打つコマンドはリスト6.1になります。

リスト6.1: Userモデルを生成する
$ rails generate model User name:string email:string
      invoke  active_record
      create    db/migrate/20140724010738_create_users.rb
      create    app/models/user.rb
      invoke    test_unit
      create      test/models/user_test.rb
      create      test/fixtures/users.yml

(コントローラ名には複数形を使い、モデル名には単数形を用いるという慣習を頭に入れておいてください。コントローラはUsersでモデルはUserです)。name:stringemail:stringオプションのパラメータを渡すことによって、データベースで使用したい2つの属性をRailsに伝えます。このときに、これらの属性の型情報も一緒に渡します (この場合はstring)。リスト3.4リスト5.28でアクション名を使用して生成した例と比較してみてください。

リスト6.1にあるgenerateコマンドの結果のひとつとして、マイグレーションと呼ばれる新しいファイルが生成されます。マイグレーションは、データベースの構造をインクリメンタルに変更する手段を提供します。それにより、要求が変更された場合にデータモデルを適合させることができます。このUserモデルの例の場合、マイグレーションはモデル生成スクリプトによって自動的に作られました。リスト6.2に示したようにnameemailの2つのカラムを持つusersテーブルを作成します (6.2.5で、マイグレーションを一から手動で作成する方法について説明します)。

リスト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 null: false
    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のマイグレーションによって作成された完全なデータモデルを6.4に示します (6.3のスケッチには無かったマジックカラムが追加されています)。

user_model_initial_3rd_edition
図6.4 リスト6.2で生成されたUserのデータモデル

マイグレーションは、以下のようにrakeコマンド (コラム2.1) を使って実行することができます。これを「マイグレーションの適用 (migrating up)」と呼びます。

$ bundle exec rake db:migrate

(2.2で、このコマンドを似たような状況で実行したことを思い出してみてください) 。初めてdb:migrateが実行されると、db/development.sqlite3という名前のファイルが生成されます。これはSQLite5データベースです。db/development.sqlite3ファイルを開くためのDB Browser for SQLiteという素晴らしいツールを使うと、データベースの構造を見ることができます (Cloud IDEを使っている場合は、6.5のようにまずはファイルをお手元にダウンロードする必要があります)。結果は6.6のようになるので、6.4と比べてみてください。6.6の中にidというマイグレーションのときに説明されなかったカラムの存在に気づいたかもしれません。2.2で簡単に説明したとおり、このカラムは自動的に作成され、Railsが各行を一意に識別するために使用します。

images/figures/sqlite_download
図6.5: Cloud IDEからファイルをダウンロードする
images/figures/sqlite_database_browser_3rd_edition
図6.6: DB Browser for SQLiteで作成したusersテーブルを確認する

Railsチュートリアルで使用されているものすべてを含め、ほとんどのマイグレーションが可逆です。これは、db:rollbackというRakeタスクで変更を取り消せることを意味します。これを“マイグレーションの取り消し (migrate down) と呼びます。

$ bundle exec rake db:rollback

(コラム3.1では、マイグレーションを元に戻すための便利なテクニックを他にも紹介しています)。上のコマンドでは、データベースからusersテーブルを削除するためにdrop_tableコマンドを内部で呼び出しています。これがうまくいくのは、changeメソッドはdrop_tablecreate_tableの逆であることを知っているからです。つまり、ロールバック用の逆方向マイグレーションを簡単に導くことができるのです。あるカラムを削除するような不可逆なマイグレーションの場合は、changeメソッドの代わりに、updownのメソッドを別々に定義する必要があります。詳細については、Railsガイドの「Active Record マイグレーション」を参照してください。

もし今の時点でデータベースのロールバックを実行していた場合は、先に進む前にもう一度以下のようにマイグレーションを適用して元に戻してください。

$ bundle exec rake db:migrate

6.1.2 modelファイル

これまで、リスト6.1のUserモデルの作成によってどのように (リスト6.2の) マイグレーションファイルが作成されるかを見てきました。そして6.3でこのマイグレーションを実行した結果を見ました。usersテーブルを作成することで、development.sqlite3という名のファイルを更新し、idnameemailcreated_atupdated_atを作成しました。また、リスト6.1ではモデル用のuser.rbも作られました。この節では、以後このモデル用ファイルを理解することに専念します。

まずは、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.13の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クラスの例 (user_example.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属性が期待どおり設定されていることがわかります。

また、Active Recordを理解する上で、「有効性 (Validity)」という概念も重要です。6.2で詳細について解説しますが、今はまず先ほどのuserオブジェクトが有効かどうか確認してみましょう。確認するためにはvalid?メソッドを使います。

>> user.valid?
true

現時点ではまだデータベースにデータは格納されていません。つまり、User.newメモリ上でオブジェクトを作成しただけで、user.valid?という行はただオブジェクトが有効かどうかを確認しただけとなります (データベースにデータがあるかどうかは有効性には関係ありません)。データベースにUserオブジェクトを保存するためには、userオブジェクトからsaveメソッドを呼び出す必要があります。

>> user.save
   (0.2ms)  begin transaction
  User Exists (0.2ms)  SELECT  1 AS one FROM "users"  WHERE LOWER("users".
  "email") = LOWER('mhartl@example.com') LIMIT 1
  SQL (0.5ms)  INSERT INTO "users" ("created_at", "email", "name", "updated_at)
   VALUES (?, ?, ?, ?)  [["created_at", "2014-09-11 14:32:14.199519"],
   ["email", "mhartl@example.com"], ["name", "Michael Hartl"], ["updated_at",
  "2014-09-11 14:32:14.199519"]]
   (0.9ms)  commit transaction
=> true

saveメソッドは、成功すればtrueを、失敗すればfalseを返します (現状では、保存はすべて成功するはずです。失敗する場合については6.2で説明します)。また、Railsコンソール上ではuser.saveに対応するSQLコマンドやその結果 (INSERT INTO "users"...) も表示するようになっています。なお、本書では生のSQLが必要になる場面がほとんどないので6、SQLコマンドに関する解説は省略します。とはいえ、Active Recordに対応するSQLコマンドをザッと眺めておくだけでも勉強にはなるはずです。

作成した時点でのユーザーオブジェクトは、id属性、マジックカラムであるcreated_at属性とupdated_at属性の値がいずれもnilであったことを思い出してください。saveメソッドを実行した後に何が変更されたのかを確認してみましょう。

>> user
=> #<User id: 1, name: "Michael Hartl", email: "mhartl@example.com",
created_at: "2014-07-24 00:57:46", updated_at: "2014-07-24 00:57:46">

idには1という値が代入され、一方でマジックカラムには現在の日時が代入されているのがわかります7。現在、作成と更新のタイムスタンプは同一ですが、6.1.5では異なる値になります。

4.4.5のUserクラスと同様に、Userモデルのインスタンスはドット記法を用いてその属性にアクセスすることができます。

>> user.name
=> "Michael Hartl"
>> user.email
=> "mhartl@example.com"
>> user.updated_at
=> Thu, 24 Jul 2014 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:
"2014-07-24 01:05:24", updated_at: "2014-07-24 01:05:24">
>> foo = User.create(name: "Foo", email: "foo@bar.com")
#<User id: 3, name: "Foo", email: "foo@bar.com", created_at: "2014-07-24
01:05:42", updated_at: "2014-07-24 01:05:42">

User.createは、truefalseを返す代わりに、ユーザーオブジェクト自身を返すことに注目してください。返されたユーザーオブジェクトは (上の2つ目のコマンドにあるfooのように) 変数に代入することもできます。

destroycreateの逆です。

>> foo.destroy
=> #<User id: 3, name: "Foo", email: "foo@bar.com", created_at: "2014-07-24
01:05:42", updated_at: "2014-07-24 01:05:42">

createと同じように、destroyはそのオブジェクト自身を返しますが、その返り値を使用しても、もう一度destroyを呼ぶことはできません。さらに、削除されたオブジェクトは、以下のようにまだメモリ上に残っています。

>> foo
=> #<User id: 3, name: "Foo", email: "foo@bar.com", created_at: "2014-07-24
01:05:42", updated_at: "2014-07-24 01:05:42">

では、オブジェクトが本当に削除されたかどうかをどのようにして知ればよいのでしょうか。そして、保存して削除されていないオブジェクトの場合、どうやってデータベースからユーザーを取得するのでしょうか。これらの問いに答えるためには、Active Recordを使ってUserオブジェクトを検索する方法について学ぶ必要があります。

6.1.4 ユーザーオブジェクトを検索する

Active Recordには、オブジェクトを検索するための方法がいくつもあります。これらの機能を使用して、過去に作成した最初のユーザーを探してみましょう。また、3番目のユーザー (foo) が削除されていることを確認しましょう。まずは存在するユーザーから探してみましょう。

>> User.find(1)
=> #<User id: 1, name: "Michael Hartl", email: "mhartl@example.com",
created_at: "2014-07-24 00:57:46", updated_at: "2014-07-24 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: "2014-07-24 00:57:46", updated_at: "2014-07-24 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: "2014-07-24 00:57:46", updated_at: "2014-07-24 00:57:46">

読んで字のごとく、firstは単にデータベースの最初のユーザーを返します。次はallメソッドです。

>> User.all
=> #<ActiveRecord::Relation [#<User id: 1, name: "Michael Hartl",
email: "mhartl@example.com", created_at: "2014-07-24 00:57:46",
updated_at: "2014-07-24 00:57:46">, #<User id: 2, name: "A Nother",
email: "another@example.org", created_at: "2014-07-24 01:05:24",
updated_at: "2014-07-24 01:05:24">]>

コンソールの出力結果を見ると、User.allでデータベースのすべてのUserオブジェクトが返ってくることがわかります。また、返ってきたオブジェクトのクラスが ActiveRecord::Relationとなっています。これは、各オブジェクトを配列として効率的にまとめてくれるクラスです(4.3.1)。

6.1.5 ユーザーオブジェクトを更新する

いったんオブジェクトを作成すれば、今度は何度でも更新したくなるものです。基本的な更新の方法は2つです。ひとつは、4.4.5でやったように属性を個別に代入する方法です。

>> user           # Just a reminder about our user's attributes
=> #<User id: 1, name: "Michael Hartl", email: "mhartl@example.com",
created_at: "2014-07-24 00:57:46", updated_at: "2014-07-24 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"

user.saveを実行したことでユーザーが更新できました。6.1.3で約束したように、マジックカラムの更新日時も更新されています。

>> user.created_at
=> "2014-07-24 00:57:46"
>> user.updated_at
=> "2014-07-24 01:37:32"

属性を更新するもうひとつの方法は、update_attributesを使うものです9

>> 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を使います。なお、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) という機能を通して、こういった制約を課すことができるようになっています (実は2.3.2で少しだけ使っていました)。ここでは、よく使われるケースのうちのいくつかについて説明します。それらは存在性 (presence)の検証、長さ (length)の検証、フォーマット (format)の検証、一意性 (uniqueness)の検証です。6.3.2では、よく使われる最終検証として確認 (confirmation)を追加します。7.3では、ユーザーが制約に違反したときに、検証機能によって自動的に表示される有用なエラーメッセージをお見せします。

6.2.1 有効性のテスト

コラム3.3で言及したとおり、テスト駆動開発は仕事で常に正しく適用できるとは限りませんが、モデルのバリデーション機能は、テスト駆動開発とまさにピッタシの機能と言えます。バリデーション機能は強力ですが、うまく動いている自信を持つのが難しいです。しかし、(テスト駆動開発のように) まず失敗するテストを書き、次にテストを成功させるように実装すると、期待した通りに動いている自信を持てるようになります。

具体的なテスト方法についてですが、まず有効なモデルのオブジェクトを作成し、その属性のうちの1つを有効でない属性に意図的に変更します。そして、バリデーションで失敗するかどうかをテストする、といった方針で進めていきます。念のため、最初に作成時の状態に対してもテストを書いておき、最初のモデルが有効であるかどうかも確認しておきます。このようにテストすることで、バリデーションのテストが失敗したとき、バリデーションの実装に問題があったのか、オブジェクトそのものに問題があったのかを確認することができます。

リスト6.1のコマンドを実行してUser用テストの原型ができているはずなので、まずはその中身から見ていきましょう (リスト6.4)。

リスト6.4: デフォルトのUserテスト (モックのみ) test/models/user_test.rb
require 'test_helper'

class UserTest < ActiveSupport::TestCase
  # test "the truth" do
  #   assert true
  # end
end

有効なオブジェクトに対してテストを書くために、setupという特殊なメソッドを使って有効なUserオブジェクト (@user) を作成します (このメソッドは第3章の演習でも少し取り上げました)。setupメソッド内に書かれた処理は、各テストが走る直前に実行されます。@userはインスタンス変数ですが、setupメソッド内で宣言しておけば、すべてのテスト内でこのインスタンス変数が使えるようになります。したがって、valid?メソッドを使ってUserオブジェクトの有効性をテストすることができます (6.1.3)。これをコードにすると、リスト6.5のようになります。

リスト6.5: 有効なUserかどうかをテストする GREEN test/models/user_test.rb
require 'test_helper'

class UserTest < ActiveSupport::TestCase

  def setup
    @user = User.new(name: "Example User", email: "user@example.com")
  end

  test "should be valid" do
    assert @user.valid?
  end
end

リスト6.5では、シンプルなassertメソッドを使ってテストします。@user.valid?trueを返すと成功し、falseを返すと失敗します。

とはいえ、Userモデルにはまだバリデーションがないので、このテストは成功するはずです。

リスト6.6: GREEN
$ bundle exec rake test:models

上ではrake test:modelsというコマンドを実行していますが、これはモデルに関するテストだけを走らせるコマンドです (5.3.4で使ったrake test:integrationと似ていることに注目してください)。

6.2.2 存在性を検証する

おそらく最も基本的なバリデーションは「存在性 (Presence)」です。これは単に、与えられた属性が存在することを検証します。たとえばこの節では、ユーザーがデータベースに保存される前にnameとemailフィールドの両方が存在することを保証します。7.3.3では、この要求を新しいユーザーを作るためのユーザー登録フォームにまで徹底させる方法を確認します。

まずはリスト6.5に、name属性の存在性に関するテストを追加します。具体的にはリスト6.7のように、まず@user変数のname属性に対して空白の文字列をセットします。そして、assert_notメソッドを使って Userオブジェクトが有効でなくなったことを確認します。

リスト6.7: name属性にバリデーションに対するテスト RED test/models/user_test.rb
require 'test_helper'

class UserTest < ActiveSupport::TestCase

  def setup
    @user = User.new(name: "Example User", email: "user@example.com")
  end

  test "should be valid" do
    assert @user.valid?
  end

  test "name should be present" do
    @user.name = "     "
    assert_not @user.valid?
  end
end

この時点では、モデルのテストは失敗するはずです。

リスト6.8: RED
$ bundle exec rake test:models

第2章の演習で少し触れましたが、name属性の存在を検査する方法は、リスト6.9に示したとおり、validatesメソッドにpresence: trueという引数を与えて使うことです。presence: trueという引数は、要素がひとつのオプションハッシュです。4.3.4のようにメソッドの最後の引数としてハッシュを渡す場合、波括弧を付けなくても問題ありません(5.1.1でも説明したように、Railsのオプションハッシュは繰り返し登場するテーマです)。

リスト6.9: name属性の存在性を検証する GREEN app/models/user.rb
class User < ActiveRecord::Base
  validates :name, presence: true
end

リスト6.9は一見魔法のように見えるかもしれませんが、validatesは単なるメソッドです。括弧を使用してリスト6.9を同等のコードに書き換えたものを以下に示します。

class User < ActiveRecord::Base
  validates(:name, presence: true)
end

コンソールを起動して、Userモデルに検証を追加した効果を見てみましょう10

$ rails console --sandbox
>> user = User.new(name: "", email: "mhartl@example.com")
>> user.valid?
=> false

このように、user変数が有効かどうかをvalid?メソッドでチェックすることができます。もしオブジェクトがひとつ以上の検証に失敗したときは、falseを返します。 また、すべてのバリデーションに通ったときにtrueを返します。今回の場合、検証が1つしかないので、どの検証が失敗したかわかります。しかし、失敗したときに作られるerrorsオブジェクトを使って確認すれば、さらに便利です。

>> user.errors.full_messages
=> ["Name can't be blank"]

(Railsが属性の存在性を検査するときに、エラーメッセージはヒントになります。これにはblank? メソッドを用います。4.4.3の終わりに見ました)。

Userオブジェクトは有効ではなくなったので、データベースに保存しようとすると自動的に失敗するはずです。

>> user.save
=> false

この変更によりリスト6.7のテストは成功しているはずです。

リスト6.10: GREEN
$ bundle exec rake test:models

リスト6.7のモデルに倣って、email属性の存在性についてもテストを書いてみましょう (リスト6.11)。最初は失敗しますが、リスト6.12のコードを追加することで成功するようになります。

リスト6.11: email属性の検証に対するテストRED test/models/user_test.rb
require 'test_helper'

class UserTest < ActiveSupport::TestCase

  def setup
    @user = User.new(name: "Example User", email: "user@example.com")
  end

  test "should be valid" do
    assert @user.valid?
  end

  test "name should be present" do
    @user.name = ""
    assert_not @user.valid?
  end

  test "email should be present" do
    @user.email = "     "
    assert_not @user.valid?
  end
end
リスト6.12: email属性の存在性を検証する GREEN app/models/user.rb
class User < ActiveRecord::Base
  validates :name,  presence: true
  validates :email, presence: true
end

これですべての存在性がチェックされたので、テストスイートは成功するはずです。

リスト6.13: GREEN
$ bundle exec rake test

6.2.3 長さを検証する

各ユーザーは、Userモデル上に名前を持つことを強制されるようになりました。しかし、これだけでは十分ではありません。ユーザーの名前はサンプルWebサイトに表示されるものなので、名前の長さにも制限を与える必要があります。6.2.2で既に同じような作業を行ったので、この実装は簡単です。

最長のユーザー名の長さに科学的な根拠はありませんので、単に50を上限として手頃な値を使うことにします。つまりここでは、51文字の名前は長すぎることを検証します。また、実際に問題になることはほとんどありませんが、問題になる可能性もあるので長すぎるメールアドレスに対してもバリデーションを掛けましょう。ほとんどのデータベースでは文字列の上限を255としているので、それに合わせて255文字を上限とします。6.2.4で説明するメールアドレスのフォーマットに関するバリデーションでは、こういった長さの検証はできないので、本節で長さに関するバリデーションを事前に追加しておきます。結果をリスト6.14に示します。

リスト6.14: nameの長さの検証に対するテスト RED test/models/user_test.rb
require 'test_helper'

class UserTest < ActiveSupport::TestCase

  def setup
    @user = User.new(name: "Example User", email: "user@example.com")
  end
  .
  .
  .
  test "name should not be too long" do
    @user.name = "a" * 51
    assert_not @user.valid?
  end

  test "email should not be too long" do
    @user.email = "a" * 244 + "@example.com"
    assert_not @user.valid?
  end
end

リスト6.14では、51文字の文字列を簡単に作るために “文字列のかけ算” を使いました。結果をコンソール上で確認できます。

>> "a" * 51
=> "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
>> ("a" * 51).length
=> 51

メールアドレスの長さに対するバリデーションも、次のように長い文字列を作成して検証します。

>> "a" * 244 + "@example.com"
=> "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaa@example.com"
>> ("a" * 244 + "@example.com").length
=> 256

この時点では、リスト6.14のテストは失敗しているはずです。

リスト 6.15: RED
$ bundle exec rake test

これをパスさせるためには、長さを強制するための検証の引数を使う必要があります。:maximumパラメータと共に用いられる:lengthは、長さの上限を強制します (リスト6.16)。

リスト6.16: name属性に長さの検証を追加する GREEN app/models/user.rb
class User < ActiveRecord::Base
  validates :name,  presence: true, length: { maximum: 50 }
  validates :email, presence: true, length: { maximum: 255 }
end

これでテストがGREENになるはずです。

リスト 6.17: GREEN
$ bundle exec rake test

成功したテストスイートを流用して、今度は少し難しい、メールアドレスのフォーマット検証作業に取りかかりましょう。

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)。このテクニックを学んだことで、基本となるメールアドレスフォーマット検証のテストを書く準備が整いました。

メールアドレスのバリデーションは扱いが難しく、エラーが発生しやすい部分なので、有効なメールアドレスと無効なメールアドレスをいくつか用意して、バリデーション内のエラーを検知していきます。具体的には、user@example,comのような無効なメールアドレスが弾かれることと、user@example.comのような有効なメールアドレスが通ることを確認しながら、バリデーションを実装していきます (ちなみに今の状態では、空でないメールアドレスであれば全て通ってしまいます) 。まずは、有効なメールアドレスをリスト6.18に示します。

リスト6.18: 有効なメールフォーマットをテストする GREEN test/models/user_test.rb
require 'test_helper'

class UserTest < ActiveSupport::TestCase

  def setup
    @user = User.new(name: "Example User", email: "user@example.com")
  end
  .
  .
  .
  test "email validation should accept valid addresses" do
    valid_addresses = %w[user@example.com USER@foo.COM A_US-ER@foo.bar.org
                         first.last@foo.jp alice+bob@baz.cn]
    valid_addresses.each do |valid_address|
      @user.email = valid_address
      assert @user.valid?, "#{valid_address.inspect} should be valid"
    end
  end
end

ここでは、assertメソッドの第2引数にエラーメッセージを追加していることに注目してください。これによって、どのメールアドレスでテストが失敗したのかを特定できるようになります。

assert @user.valid?, "#{valid_address.inspect} should be valid"

(詳細な文字列を調べるために4.3.3で紹介したinspectメソッドを使っています。) どのメールアドレスで失敗したのかを知ることは非常に便利です。そこでリスト6.18では、eachメソッドを使って各メールアドレスを順にテストしています。ループさせずにテストすると、失敗した行番号からとメールアドレスの行数を照らし合わせて、失敗したメールアドレスを特定するといった作業が発生してしまいます。

次に、 user@example,com (ドットではなくカンマになっている) やuser_at_foo.org (アットマーク ‘@’ がない) といった無効なメールアドレスを使って 「無効性 (Invalidity)」についてテストしていきます。リスト6.18と同様に、リスト6.19でもエラーメッセージをカスタマイズして、どのメールアドレスで失敗したのかすぐに特定できるようにしておきます。

リスト6.19: メールフォーマットの検証に対するテスト RED test/models/user_test.rb
require 'test_helper'

class UserTest < ActiveSupport::TestCase

  def setup
    @user = User.new(name: "Example User", email: "user@example.com")
  end
  .
  .
  .
  test "email validation should reject invalid addresses" do
    invalid_addresses = %w[user@example,com user_at_foo.org user.name@example.
                           foo@bar_baz.com foo@bar+baz.com]
    invalid_addresses.each do |invalid_address|
      @user.email = invalid_address
      assert_not @user.valid?, "#{invalid_address.inspect} should be invalid"
    end
  end
end

この時点では、テストは失敗するはずです。

リスト6.20: RED
$ bundle exec rake test

メールアドレスのフォーマットを検証するためには、次のようにformatというオプションを使います。

validates :email, format: { with: /<regular expression>/ }

このオプションは引数に正規表現 (Regular Expression) (regexとも呼ばれます) を取ります。正規表現は一見謎めいて見えますが、文字列のパターンマッチングにおいては非常に強力な言語です。つまり、有効なメールアドレスだけにマッチして、無効なメールアドレスにはマッチしない正規表現を組み立てる必要があります。

メールアドレス標準を定める公式サイトに完全な正規表現があるのですが、非常に巨大かつ意味不明で、場合によっては逆効果になりかねるので11、本チュートリアルではもっと実用的で、堅牢であることが実戦で保証されている正規表現を採用します。 これが、その正規表現です。

VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i

この正規表現を理解するために、お手頃なサイズに分割して6.1にまとめました12

表現 意味
/\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i (完全な正規表現)
/ 正規表現の開始を示す
\A 文字列の先頭
[\w+\-.]+ 英数字、アンダースコア (_)、プラス (+)、ハイフン (-)、ドット (.) のいずれかを少なくとも1文字以上繰り返す
@ アットマーク
[a-z\d\-.]+ 英小文字、数字、ハイフン、ドットのいずれかを少なくとも1文字以上繰り返す
\. ドット
[a-z]+ 英小文字を少なくとも1文字以上繰り返す
\z 文字列の末尾
/ 正規表現の終わりを示す
i 大文字小文字を無視するオプション
表6.1 メールの正規表現を分解した結果

6.1からも多くのことを学べるとは思いますが、正規表現を本当に理解するためには実際に使って見るのが一番です。たとえばRubularという対話的に正規表現を試せるWebサイトがあります (6.7)13。このWebサイトはインタラクティブ性に富んだインターフェイスを持っていて、また、正規表現のクイックリファレンスも兼ね備えています。Rubularをブラウザで開き、6.1の内容を実際に試してみることを強くお勧めします。正規表現は、読んで学ぶより対話的に学んだほうが早いです。(: 6.1の正規表現をRubularで使う場合、冒頭の\Aと末尾の\zの文字は含めないでください)。

images/figures/rubular
図6.4 素晴らしい正規表現エディタRubular

6.1の正規表現を適用してemailのフォーマットを検証した結果を、リスト6.21に示します。

リスト6.21: メールフォーマットを正規表現で検証する GREEN 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, length: { maximum: 255 },
                    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, length: { maximum: 255 },
                    format: { with: VALID_EMAIL_REGEX }

このパターンに一致するメールアドレスだけが有効であることをチェックします。上の正規表現には少しだけ残念な点があります。foo@bar..comのようなドットの連続を誤りとして検出できません。この問題の修正するにはとてつもなく複雑な正規表現を使う必要がありますが、これは演習問題に回します (6.5)。

この時点で、テストは成功しているはずです。

リスト6.22: GREEN
$ bundle exec rake test:models

残る制約は、メールアドレスが一意であることを強制するだけとなりました。

6.2.5 一意性を検証する

メールアドレスの一意性を強制するために (ユーザー名として使うために)、validatesメソッドの:uniqueオプションを使います。ただしここで重大な警告があります。以下の文面は流し読みせず、必ず注意深く読んでください。

まずは小さなテストから書いていきます。モデルのテストではこれまで、主にUser.newを使ってきました。このメソッドは単にメモリ上にRubyのオブジェクトを作るだけです。しかし、一意性のテストのためには、メモリ上だけではなく、実際にレコードをデータベースに登録する必要があります14。そのため、まずは重複したメールアドレスからテストしていきます (リスト6.23)。

リスト6.23: 重複するメールアドレス拒否のテスト RED test/models/user_test.rb
require 'test_helper'

class UserTest < ActiveSupport::TestCase

  def setup
    @user = User.new(name: "Example User", email: "user@example.com")
  end
  .
  .
  .
  test "email addresses should be unique" do
    duplicate_user = @user.dup
    @user.save
    assert_not duplicate_user.valid?
  end
end

上のコードでは、@userと同じメールアドレスのユーザーは作成できないことを、@user.dupを使ってテストしています。dupは、同じ属性を持つデータを複製するためのメソッドです。@userを保存した後では、複製されたユーザーのメールアドレスが既にデータベース内に存在するため、ユーザの作成は無効になるはずです。

リスト6.23のテストをパスさせるために、emailのバリデーションにuniqueness: trueというオプションを追加します リスト6.24

リスト6.24: メールアドレスの一意性を検証する GREEN 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, length: { maximum: 255 },
                    format: { with: VALID_EMAIL_REGEX },
                    uniqueness: true
end

実装の途中ですが、ここでひとつ補足します。通常、メールアドレスでは大文字小文字が区別されません。すなわち、foo@bar.comFOO@BAR.COMFoO@BAr.coMと書いても扱いは同じです。従って、メールアドレスの検証ではこのような場合も考慮する必要があります15 。このため、大文字を区別しないでテストすることが肝要になり、実際のコードはリスト 6.25のようになります。

リスト6.25: 大文字小文字を区別しない、一意性のテスト RED test/models/user_test.rb
require 'test_helper'

class UserTest < ActiveSupport::TestCase

  def setup
    @user = User.new(name: "Example User", email: "user@example.com")
  end
  .
  .
  .
  test "email addresses should be unique" do
    duplicate_user = @user.dup
    duplicate_user.email = @user.email.upcase
    @user.save
    assert_not duplicate_user.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"
>> duplicate_user = user.dup
>> duplicate_user.email = user.email.upcase
>> duplicate_user.valid?
=> true

現在の一意性検証では大文字小文字を区別しているため、duplicate_user.valid?trueになります。しかし、ここではfalseになる必要があります。幸い、:uniquenessでは:case_sensitiveという打ってつけのオプションが使用できます (リスト6.26)。

リスト6.26: メールアドレスの大文字小文字を無視した一意性の検証 GREEN 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, length: { maximum: 255 },
                    format: { with: VALID_EMAIL_REGEX },
                    uniqueness: { case_sensitive: false }
end

ここで、リスト6.24truecase_sensitive: falseに置き換えただけであることに注目してください (リスト6.26)。Railsはこの場合、:uniquenesstrueと判断します。

この時点で、アプリケーションは重要な警告と共にメールアドレスの一意性を強制し、テストスイートもパスするはずです。

リスト 6.27: GREEN
$ bundle exec rake test

しかし、依然としてここには1つの問題が残っています。それはActive Recordはデータベースのレベルでは一意性を保証していないという問題です。具体的なシナリオを使ってその問題を説明します。

  1. アリスはサンプルアプリケーションにユーザー登録します。メールアドレスはalice@wonderland.comです。
  2. アリスは誤って “Submit” を素早く2回クリックしてしまいます。そのためリクエストが2つ連続で送信されます。
  3. 次のようなことが順に発生します。リクエスト1は、検証にパスするユーザーをメモリー上に作成します。リクエスト2でも同じことが起きます。リクエスト1のユーザーが保存され、リクエスト2のユーザーも保存されます。
  4. この結果、一意性の検証が行われているにもかかわらず、同じメールアドレスを持つ2つのユーザーレコードが作成されてしまいます。

上のシナリオが信じがたいもののように思えるかもしれませんが、どうか信じてください。RailsのWebサイトでは、トラフィックが多いときにこのような問題が発生する可能性があるのです (筆者もこれを理解するのに苦労しました)。幸い、解決策の実装は簡単です。実は、この問題はデータベースレベルでも一意性を強制するだけで解決します。具体的にはデータベース上のemailのカラムにインデックス (index)を追加し (コラム6.2)、そのインデックスが一意であるようにすれば解決します。

コラム6.2 データベースのインデックス

データベースにカラムを作成するとき、そのカラムでレコードを検索する (find) 必要があるかどうかを考えることは重要です。たとえば、リスト6.2のマイグレーションによって作成されたemail属性について考えてみましょう。第7章ではユーザーをサンプルアプリにログインできるようにしますが、このとき、送信されたものと一致するメールアドレスのユーザーのレコードをデータベースの中から探しだす必要があります。残念なことに、(インデックスなどの機能を持たない) 素朴なデータモデルにおいてユーザーをメールアドレスで検索するには、データベースのひとりひとりのユーザーの行を端から順に読み出し、そのemail属性と与えられたメールアドレスを比較するという非効率的な方法しかありません。つまり、たとえばデータベース上の最後のユーザを探す場合でも、すべてのユーザーを最初から順に一人ずつ探していくことになります。これは、データベースの世界では全表スキャンとして知られており、数千のユーザーがいる実際のサイトでは極めて不都合です。

emailカラムにインデックスを追加することで、この問題を解決することができます。データベースのインデックスを理解するためには、本の索引との類似性を考えるとよいでしょう。索引のない本では、与えられた言葉 (例えば、“foobar”) が出てくる箇所をすべて見つけるためには、ページを端から順にめくって最後まで探す必要があります (紙バージョンの全表スキャン)。しかし索引のある本であれば、“foobar”を含むすべてのページを索引の中から探すだけで済みます。データベースのインデックスも本質的には本の索引と同じように動作します。

emailインデックスを追加すると、データモデリングの変更が必要になります。Railsでは (6.1.1で見たように) マイグレーションでインデックスを追加します。6.1.1で、Userモデルを生成すると自動的に新しいマイグレーションが作成されたことを思い出してください (リスト6.2)。今回の場合は、既に存在するモデルに構造を追加するので、以下のようにmigrationジェネレーターを使用してマイグレーションを直接作成する必要があります。

$ rails generate migration add_index_to_users_email

ユーザー用のマイグレーションと異なり、メールアドレスの一意性のマイグレーションは未定義になっています。リスト6.28のように定義を記述する必要があります16

リスト6.28: メールアドレスの一意性を強制するためのマイグレーション 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用のサンプルデータが含まれている) fixtures内で一意性の制限が保たれていないため、テストは失敗します。リスト6.1でユーザー用のfixtureが自動的に生成されていますが、メールアドレスが一意になっていません (リスト6.29)。(このデータはいずれも有効ではありませんが、fixture内のサンプルデータはバリデーションを通っていなかったので今まで問題にはなりませんでした。)

リスト6.29: Userのデフォルトfixture RED test/fixtures/users.yml
# Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/
# FixtureSet.html

one:
  name: MyString
  email: MyString

two:
  name: MyString
  email: MyString

また、このfixtureは第8章になるまで使わない予定なので、今のところはこれらのデータを削除しておき、ユーザー用のfixtureファイルを空にしておきましょう (リスト6.30)。

リスト6.30: 空のfixtureファイル GREEN test/fixtures/users.yml
# empty

これで1つの問題が解決されましたが、メールアドレスの一意性を保証するためには、もう1つやらなければならないことがあります。それは、「いくつかのデータベースのアダプタは大文字小文字を区別するインデックスを使っている」という問題への対処です。例えば“Foo@ExAMPle.Com”と“foo@example.com”が別々の文字列だと解釈してしまうデータベースがありますが、私達のアプリケーションではこれらの文字列は同一であると解釈されるべきです。この問題を避けるために、今回は「データベースに保存される直前にすべての文字列を小文字に変換する」という対策を採ります。例えば“Foo@ExAMPle.CoM”という文字列が与えられたら、保存する直前に“foo@example.com”に変換してしまいます。これを実装するためにActive Recordのコールバック (callback) メソッドを利用します。このメソッドは、ある特定の時点で呼び出されるメソッドです。今回の場合は、オブジェクトが保存される時点で処理を実行したいので、before_saveというコールバックを使います。これを使って、ユーザーをデータベースに保存する前にemail属性を強制的に小文字に変換します17。これをコードにすると、リスト6.31のようになります。(本チュートリアルで初めて紹介したテクニックですが、このテクニックについては第10章でもう一度取り上げます。そこではコールバックを定義するときにメソッドを参照するという慣習について説明します。)

リスト6.31: email属性を小文字に変換してメールアドレスの一意性を保証する GREEN app/models/user.rb
class User < ActiveRecord::Base
  before_save { self.email = email.downcase }
  validates :name,  presence: true, length: { maximum: 50 }
  VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i
  validates :email, presence: true, length: { maximum: 255 },
                    format: { with: VALID_EMAIL_REGEX },
                    uniqueness: { case_sensitive: false }
end

リスト6.31のコードは、before_saveコールバックにブロックを渡してユーザーのメールアドレスを設定します。設定されるメールアドレスは、現在の値をStringクラスのdowncaseメソッドを使って小文字バージョンにしたものです。メールアドレスの小文字変換に対するテストは演習として残しておきます (6.5)。

リスト6.31では、次のように代入をしていましたが、

self.email = self.email.downcase

Userモデルの中では、右式でselfというキーワードは省略できます (ちなみにここのselfは現在のユーザーを指します)。したがって、次のように書くこともできます。

self.email = email.downcase

実は4.4.2palindrome内でreverse メソッドを使っていたときも、同様のケースであったことを思い出してください。そのときと同様で、左式ではselfを省略することはできません。したがって、

email = email.downcase

と書くとうまく動きません。(このトピックについては、8.4でより深く解説していきます。)

これで、先に述べたアリスのシナリオはうまくいくようになります。データベースは、最初のリクエストに基づいてユーザーのレコードを保存しますが、2度目の保存は一意性の制約に反するので拒否します(Railsのログにエラーが出力されますが、害は生じません)。さらに、インデックスをemail属性に追加したことで、6.1.4で挙げた2番目の目標である「多数のデータがあるときの検索効率を向上させる」も達成されました。これは、email属性にインデックスを付与したことによって、メールアドレスからユーザーを引くときに全表スキャンを使わずに済むようになったためです (コラム6.2)。

6.3 セキュアなパスワードを追加する

ユーザー属性の「名前」と「メールアドレス」に対してバリデーションを追加したので、最後の砦である「セキュアなパスワード」に取り掛かります。セキュアパスワードという手法では、各ユーザーにパスワードとパスワードの確認を入力させ、それを (そのままではなく) ハッシュ化したものをデータベースに保存します。(ハッシュ化というと少し困惑してしまうかもしれません。4.3.3ではハッシュとはRubyのデータ構造であると説明しましたが、今回の「ハッシュ化」とはそういった構造ではなく、ハッシュ関数を使って入力されたデータを元に戻せない (不可逆な) データにする処理を指します。) また、入力されたパスワードを使用してユーザーを認証する手段と、第8章で使用する、ユーザーがサイトにログインできるようにする手段も提供します。

ユーザーの認証は、パスワードの送信、ハッシュ化、データベース内のハッシュ化された値との比較、という手順で進んでいきます。比較の結果が一致すれば、送信されたパスワードは正しいと認識され、そのユーザーは認証されます。ここで、生のパスワードではなく、ハッシュ化されたパスワード同士を比較していることに注目してください。こうすることで、生のパスワードをデータベースに保存するという危険なことをしなくてもユーザーを認証できます。これで、仮にデータベースの内容が盗まれたり覗き見されるようなことがあっても、パスワードの安全性が保たれます。

6.3.1 ハッシュ化されたパスワード

セキュアなパスワードの実装は、has_secure_passwordというRailsのメソッドを呼び出すだけでほとんど終わってしまいます。このメソッドは、Userモデルで次のように呼び出せます。

class User < ActiveRecord::Base
  .
  .
  .
  has_secure_password
end

上のようにモデルにこのメソッドを追加すると、次のような機能が使えるようになります。

  • セキュアにハッシュ化したパスワードを、データベース内のpassword_digestという属性に保存できるようになる。
  • 2つのペアの仮想的な属性18 (passwordpassword_confirmation)が使えるようになる。また、存在性と値が一致するかどうかのバリデーションも追加される。
  • authenticateメソッドが使えるようになる (引数の文字列がパスワードと一致するとUserオブジェクトを、間違っているとfalse返すメソッド)。

この魔術的なhas_secure_password機能を使えるようにするには、1つだけ条件があります。それは、モデル内にpassword_digestという属性が含まれていることです。ちなみにdigestという言葉は、暗号化用ハッシュ関数という用語が語源です。したがって、今回の文脈ではハッシュ化されたパスワード暗号化されたパスワードは似た表現となります19。今回はUserモデルで使うので、Userのデータモデルは以下の図のようになります (6.8)。

user_model_password_digest_3rd_edition
図6.8 Userモデルにpassword_digest属性を追加する

6.8のようなデータモデルにするために、まずはpassword_digestカラム用の適切なマイグレーションを生成します。マイグレーション名は自由に指定できますが、上のように末尾をto_usersにしておくことをお勧めします。こうしておくと、usersテーブルにカラムを追加するマイグレーションがRailsによって自動的に作成されるからです。add_password_digest_to_usersというマイグレーションファイルを生成するためには、以下のコマンドを実行します。

$ rails generate migration \
add_password_digest_to_users password_digest:string

上のコマンドではpassword_digest:stringという引数を与えて、今回必要になる属性名と型情報を渡しています。リスト6.1usersテーブルを最初に生成するとき、name:stringemail:stringといった引数を与えていたことを思い出してください。そのときと同様にpassword_digest:stringという引数を与えることで、完全なマイグレーションを生成するための十分な情報をRailsに与えることができます (リスト6.32)。

リスト6.32: usersテーブルにpassword_digestカラムを追加するマイグレーション db/migrate/[timestamp]_add_password_digest_to_users.rb
class AddPasswordDigestToUsers < ActiveRecord::Migration
  def change
    add_column :users, :password_digest, :string
  end
end

リスト6.32では、add_columnメソッドを使ってusersテーブルにpassword_digestカラムを追加しています。これを適用させるには、データベースでマイグレーションを実行します。

$ bundle exec rake db:migrate

また、has_secure_passwordを使ってパスワードをハッシュ化するためには、最先端のハッシュ関数であるbcryptが必要になります。パスワードを適切にハッシュ化することで、たとえ攻撃者によってデータベースからパスワードが漏れてしまった場合でも、Webサイトにログインされないようにできます。サンプルアプリケーションでbcryptを使用するために、bcrypt-ruby gemをGemfileに追加します (リスト6.33)。

リスト6.33: bcryptGemfileに追加する
source 'https://rubygems.org'

gem 'rails',                '4.2.2'
gem 'bcrypt',               '3.1.7'
.
.
.

次に、いつものようにbundle installを実行します。

$ bundle install

6.3.2 ユーザーがセキュアなパスワードを持っている

Userモデルにpassword_digest属性を追加し、Gemfileにbcryptを追加したことで、ようやくUserモデル内でhas_secure_passwordが使えるようになりました (リスト6.34)。

リスト6.34: Userモデルにhas_secure_passwordを追加する RED app/models/user.rb
class User < ActiveRecord::Base
  before_save { self.email = email.downcase }
  validates :name, presence: true, length: { maximum: 50 }
  VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i
  validates :email, presence: true, length: { maximum: 255 },
                    format: { with: VALID_EMAIL_REGEX },
                    uniqueness: { case_sensitive: false }
  has_secure_password
end

リスト6.34REDと記しておいたように、まだテストは落ちたままになっているはずです。コマンドラインで以下を実行して確認してください。

リスト6.35: RED
$ bundle exec rake test

テストが失敗する理由は、6.3.1で触れたようにhas_secure_passwordには、仮想的なpassword属性とpassword_confirmation属性に対してバリデーションをする機能も(強制的に)追加されているからです。しかしリスト6.25のテストでは、@user 変数にこのような値がセットされておりません。

def setup
  @user = User.new(name: "Example User", email: "user@example.com")
end

テストをパスさせるために、リスト6.36のようにパスワードとパスワード確認の値を追加します。

リスト6.36: パスワードとパスワード確認を追加する GREEN test/models/user_test.rb
require 'test_helper'

class UserTest < ActiveSupport::TestCase

  def setup
    @user = User.new(name: "Example User", email: "user@example.com",
                     password: "foobar", password_confirmation: "foobar")
  end
  .
  .
  .
end

これでテストが成功するようになります。

リスト6.37: GREEN
$ bundle exec rake test

Userモデルに対してhas_secure_passwordを追加する利点は6.3.4で少しだけ説明しますが、 その前に、パスワードの最小文字数を設定する方法について説明します。

6.3.3 パスワードの最小文字数

パスワードを簡単に当てられないようにするために、パスワードの最小文字数を設定しておくことは一般に実用的です。Railsでパスワードの長さを設定する方法はたくさんありますが、今回は簡潔にパスワードが空でないことと最小文字数 (6文字) の2つを設定しましょう。パスワードの長さが6文字以上であることを検証するテストを、以下のリスト6.38に示します。

リスト6.38: パスワードの最小文字数をテストする RED test/models/user_test.rb
require 'test_helper'

class UserTest < ActiveSupport::TestCase

  def setup
    @user = User.new(name: "Example User", email: "user@example.com",
                     password: "foobar", password_confirmation: "foobar")
  end
  .
  .
  .
  test "password should be present (nonblank)" do
    @user.password = @user.password_confirmation = " " * 6
    assert_not @user.valid?
  end

  test "password should have a minimum length" do
    @user.password = @user.password_confirmation = "a" * 5
    assert_not @user.valid?
  end
end

ここで、以下のような多重代入 (Multiple Assignment) を使っていることに注目してください。

@user.password = @user.password_confirmation = "a" * 5

リスト6.38のこの行では、パスワードとパスワード確認に対して同時に代入をしています (このケースでは、リスト6.14と同じように、文字列の乗算を利用して5文字の文字列を代入しています)。

リスト6.16ではmaximumを使ってユーザー名の最大文字数を制限していましたが、これと似たような形式のminimumというオプションを使って、最小文字数のバリデーションを実装することができます。

validates :password, length: { minimum: 6 }

また、空のパスワードを入力させないために、存在性のバリデーション (6.2.2) も一緒に追加します。結果として、Userモデルのコードはリスト6.39のようになります。(has_secure_passwordメソッドは存在性のバリデーションもしてくれるのですが、これは新しくレコードが追加されたときだけに適用されます。したがって、たとえばユーザーが " " (6文字分の空白スペース) といった文字列をパスワード欄に入力して更新しようとすると、バリデーションが適用されずに更新されてしまう問題が発生してしまいます。)

リスト6.39: セキュアパスワードの完全な実装 GREEN app/models/user.rb
class User < ActiveRecord::Base
  before_save { self.email = email.downcase }
  validates :name, presence: true, length: { maximum: 50 }
  VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i
  validates :email, presence: true, length: { maximum: 255 },
                    format: { with: VALID_EMAIL_REGEX },
                    uniqueness: { case_sensitive: false }
  has_secure_password
  validates :password, presence: true, length: { minimum: 6 }
end

この時点で、テストは成功するはずです。

リスト6.40: GREEN
$ bundle exec rake test:models

6.3.4 ユーザーの作成と認証

以上でUserモデルの基本部分が完了しましたので、今度は7.1でユーザー情報表示ページを作成するときに備えて、データベースに新規ユーザーを1人作成しましょう。また、Userモデルにhas_secure_passwordを追加した効果についても (たとえばauthenticateメソッドの効果なども) 具体的に見ていきます。

ただしWebからのユーザー登録はまだできない (第7章で完成させます) ので、今回はRailsコンソールを使ってユーザーを手動で作成することにしましょう。6.1.3で説明したcreateを使いますが、後々実際のユーザーを作成する必要が出てくるので、今回はサンドボックス環境は使いません。したがって、今回作成したユーザーを保存すると、データベースに反映されます。それでは、まずrails consoleコマンドを実行してセッションを開始し、次に有効な名前・メールアドレス・パスワード・パスワード確認を渡してユーザーを作成してみましょう。

$ rails console
>> User.create(name: "Michael Hartl", email: "mhartl@example.com",
?>             password: "foobar", password_confirmation: "foobar")
=> #<User id: 1, name: "Michael Hartl", email: "mhartl@example.com",
created_at: "2014-09-11 14:26:42", updated_at: "2014-09-11 14:26:42",
password_digest: "$2a$10$sLcMI2f8VglgirzjSJOln.Fv9NdLMbqmR4rdTWIXY1G...">

うまくデータベースに保存されたかどうかを確認するためには、開発環境用のデータベースをDB Browser for SQLiteで開き、 usersテーブルの中身を見ます (6.9)20。もしCloud IDEを使っている場合は、データベースのファイルをダウンロードして開いてください (6.5)。このとき、先ほど定義したUserモデルの属性 (6.8) に対応したカラムがあることにも注目しておいてください

images/figures/sqlite_user_row_with_password_3rd_edition
図6.9 SQLiteデータベースdb/development.sqlite3に登録されたユーザーの行

コンソールに戻ってpassword_digest属性を参照してみると、リスト6.39has_secure_passwordの効果を確認できます。

>> user = User.find_by(email: "mhartl@example.com")
>> user.password_digest
=> "$2a$10$YmQTuuDNOszvu5yi7auOC.F4G//FGhyQSWCpghqRWQWITUYlG3XVy"

これは、Userオブジェクトを作成したときに、"foobar"という文字列がハッシュ化された結果です。bcryptを使って生成されているので、この文字列から元々のパスワードを導出することは、コンピュータを使っても非現実的です21

また6.3.1で説明したように、has_secure_passwordをUserモデルに追加したことで、そのオブジェクト内でauthenticateメソッドが使えるようになっています