Ruby on Rails チュートリアル
-
第3版 目次
- 第1章 ゼロからデプロイまで
- 第2章 Toyアプリケーション
- 第3章 ほぼ静的なページの作成
- 第4章 Rails風味のRuby
- 第5章 レイアウトを作成する
- 第6章 ユーザーのモデルを作成する
- 第7章 ユーザー登録
- 第8章 ログイン、ログアウト
- 第9章 ユーザーの更新・表示・削除
- 第10章 アカウント有効化とパスワード再設定
- 第11章 ユーザーのマイクロポスト
- 第12章 ユーザーをフォローする
|
||
第3版 目次
|
||
最新版を読む |
Ruby on Rails チュートリアル
プロダクト開発の0→1を学ぼう
下記フォームからメールアドレスを入力していただくと、招待リンクが記載されたメールが届きます。リンクをクリックし、アカウントを有効化した時点から『30分間』解説動画のお試し視聴ができます。
メール内のリンクから視聴を開始できます。
第3版 目次
- 第1章 ゼロからデプロイまで
- 第2章 Toyアプリケーション
- 第3章 ほぼ静的なページの作成
- 第4章 Rails風味のRuby
- 第5章 レイアウトを作成する
- 第6章 ユーザーのモデルを作成する
- 第7章 ユーザー登録
- 第8章 ログイン、ログアウト
- 第9章 ユーザーの更新・表示・削除
- 第10章 アカウント有効化とパスワード再設定
- 第11章 ユーザーのマイクロポスト
- 第12章 ユーザーをフォローする
第3章 ほぼ静的なページの作成
本章から、本格的なサンプルアプリケーションの開発を進めていきます。残りのチュートリアルでは、このアプリケーションを例題として扱って学習していくことになります。本書を通して開発するアプリケーションは、最終的にはユーザーやマイクロポスト、ログイン/ログアウトなどの認証機能を持ちますが、まずは簡単なトピックである「静的なページの作成」から始めます。非常に単純なページではありますが、静的なページを自分の手で作成することは良い経験になり、多くの示唆も得られます。私達がこれから開発するアプリケーションにとって最適なスタート地点といえるでしょう。
Rails はデータベースと連携して動的なWebサイトを開発するように設計されていますが、HTMLファイルだけで構成されている静的なページを作ることもできます。実際、Railsであえて静的なページを使用しておいて、後からほんの少し動的なコンテンツを追加することもできます。本章では、このような静的なページの作成について学んでいきます。本章ではそれと平行して、近年のプログラミングで不可欠となっている「自動化テスト」の雰囲気を掴んでいただきます。自動化テストを作成することで、コードが正しく動いていることが裏付けられます。さらに、良いテストを書くことで、自信をもってリファクタリングを行うことができます。たとえば、フォームの振る舞いを変更せずに、フォーム内で使われている関数を書き換えたいときに有用です。
3.1 セットアップ
第2章と同様に、新しいRailsプロジェクトを作成するところから始めます。今回はsample_app
という名前にします (リスト3.1)1。リスト3.1のコマンドを実行したときに「Could not find ’railties'」のようなエラーが発生した場合は、Railsのバージョンが正しくない可能性がありますので、リスト1.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になります)。
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に記入します。
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.4でgit 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にデプロイしておくとよいでしょう。第1章や第2章と同様に、リスト1.8や1.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ページに使用するアクションもそれぞれ作成することにし、アクション名はすべて小文字のhome
、help
、about
にします。generate
スクリプトではアクション名をまとめて指定することもできるので、コマンドラインでHomeページとHelpページ用のアクションもまとめて生成することにします。なお、Aboutページだけは学習のため、あえてコマンドラインでは作成せず、3.3で手動で追加することにします。これらの要素を盛り込んだStaticPagesコントローラ生成コマンドと実行結果をリスト3.4に示します。
$ 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 g
は rails 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 |
次に進む前に、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で元に戻す方法を紹介しています。
どれほど十分に気を付けていたとしても、Railsアプリケーションの開発中に何か失敗してしまうことはありえます。ありがたいことに、Railsにはそのような失敗をカバーする機能がいくつもあります。
一般的なシナリオの1つは、生成したコードを元に戻したい場合です。たとえば、コントローラを生成した後で、もっといいコントローラ名を思い付き、生成したコードを削除したくなった場合などです。リスト3.4のように、Railsはコントローラ以外にも関連ファイルを大量に生成するので、生成されたコントローラファイルを削除するだけでは元に戻りません。自動生成されたコードを完全に元に戻すには、新規作成されたファイルを削除するほかに、既存のファイルに挿入されたコードも削除する必要があります (実際、2.2や2.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)。
先ほどリスト3.4のようにhome
アクションとhelp
アクションを生成したので、routesファイルにはそれぞれのアクションで使用されるルールが定義されています (リスト3.5)。
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)。
HTTP (HyperText Transfer Protocol) には4つの基本的な操作があり、それぞれGET、POST、PATCH、DELETEという4つの動詞に対応づけられています。クライアント (通常、FirefoxやSafariなどのWebブラウザ) とサーバー (ApacheやNginxなどのWebサーバー) は、上で述べた4つの基本操作を互いに認識できるようになっています(ローカル環境でRailsアプリケーションを開発しているときは、クライアントとサーバーが同じコンピュータ上で動いていますが、一般的には、それぞれ別のコンピュータで動作しているという点を理解しておいてください)。Railsを含む多くのWebフレームワークは、HTTPの各操作を発展させたREST アーキテクチャの影響を受けています。第2章でも簡単に触れましたが、第7章では、より深い内容について学びます。
GET は、最も頻繁に使用されるHTTP操作で、主にWeb上のデータを読み取る際に使われます。「ページを取得する」という意味のとおり、ブラウザはgoogle.comやwikipedia.orgなどのWebサイトを開くたびにGETリクエストをサイトに送信します。Railsアプリケーションでは、POSTリクエストは何かを作成するときによく使われます (なお本来のHTTPでは、POSTを更新に使ってもよいとしています)。たとえば、ユーザー登録フォームで新しいユーザーを作成するときは、POSTリクエストを送信します。他にも、PATCHと DELETEという2つの操作があり、それぞれサーバー上の何かを更新したり削除したりするときに使われます。これら2つの操作は、GETやPOSTほどは使用されていません。これは、ブラウザがPATCHとDELETEをネイティブでは送信しないからです。しかし、Ruby on Railsなどの多くのWebフレームワークは、ブラウザがこれらの操作のリクエストを送信しているかのように見せかける技術 (偽装) を駆使して、PATCHとDELETEという操作を実現しています。結果として、Railsはこの4つのHTTPリクエスト (GET・POST・PATCH・DELETE) を全てサポートできるようになりました。
このページがどのようにして表示されるのかを理解するために、まずはテキストエディタでStaticPagesコントローラを開いてみましょう。リスト3.6のような内容になっているはずです。ここで、第2章のUsersコントローラやMicropostsコントローラとは異なり、StaticPagesコントローラは一般的なRESTアクションに対応していないことに注意してください。これは、静的なページの集合に対しては、適切なアクションと言えます。言い換えると、RESTアーキテクチャは、あらゆる問題に対して最適な解決方法であるとは限らないということです。
class StaticPagesController < ApplicationController
def home
end
def help
end
end
リスト 3.6のclass
というキーワードから、static_pages_controller.rb
はStaticPagesController
というクラスを定義していることがわかります。クラスは、関数 (メソッドとも呼ばれます) をまとめるときに便利な手法です。今回の例では、def
というキーワードを使って、home
アクションやhelp
アクションを定義しています。2.3.4で説明したように、山括弧<
は、StaticPagesController
がApplicationController
という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)。
<h1>StaticPages#home</h1>
<p>Find me in app/views/static_pages/home.html.erb</p>
help
アクションに対応するビューも、上のコードと似ています (リスト3.8)。
<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がその一例です。
<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>
<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.3と図3.4に示します。
3.3 テストから始める
3.2.2でサンプルアプリのHomeページとHelpページを作成して中身も書き加えたので、今度はAboutページを同様に追加します。何らかの変更を行う際には、常に「自動化テスト」を作成して、機能が正しく実装されたことを確認する習慣をぜひ身に付けましょう。アプリケーションを開発しながらテストスイートをみっちり作成しておけば、いざというときのセーフティネットにもなり、それ自体がアプリケーションのソースコードの「実行可能なドキュメント」にもなります。テストを作成するということは、その分コードを余分に書くことになりますが、正しく行えば、むしろテストがないときよりも確実に開発速度がアップします。テストが揃っていれば、バグを追うために余分な時間を使わずに済むためです。そんなふうにうまくいくとは信じられない人もいるかもしれませんが、一度でもテスト作成が上達すれば間違いなくこのとおりになります。だからこそ、テスト作成の習慣をできるだけ早いうちに身につけることが重要なのです。
テストが重要であるという点ではRails開発者の意見はほぼ一致していますが、細かい点では異論が生じているのも確かです。特に、テスト駆動開発 (TDD)6 (テストの手法のひとつ: 最初に「正しいコードがないと失敗するテスト」を書き、次に本編のコードを書いてそのテストがパスするようにする) の是非については、当分議論が終わりそうにありません。筆者も悩んだ末、Ruby on Railsチュートリアルではこの点について「とにかく〜すべし」的な原理主義を避けることにしました。テストに関しては、原則として手軽かつ直感的なアプローチを採用し、必要に応じてTDDに切り替えるようにしています (コラム3.3)。
それではいつ、どんなふうにテストを行えばよいのでしょうか。この点を理解するために、テストを行う目的をもう一度確認してみましょう。著者は、テストには以下の3つのメリットがあると考えます。
- テストが揃っていれば、機能停止に陥るような回帰バグ (regression: 以前のバグが再発したり機能追加/変更の副作用が生じたりすること、先祖返りとも言う) を防止できる。
- テストが揃っていれば、コードを安全にリファクタリング (機能を変更せずにコードを改善すること) できる。
- テストコードは、アプリケーションコードから見ればクライアントとして動作するので、アプリケーションの設計やシステムの他の部分とのインターフェイスを決めるときにも役に立つ。
上の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)。
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。
$ 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に示します。
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」に変わっている点だけです。
テストを実行すると、期待どおり失敗します。
$ bundle exec rake test
3 tests, 2 assertions, 0 failures, 1 errors, 0 skips
3.3.3 Green
テストがめでたく失敗した (RED) ので、今度はこのテストのエラーメッセージを頼りにテストがパスする (GREEN) ようにコードを書くことで、Aboutページを実装します。
失敗したテストのエラーメッセージをもっと詳しく見ていきましょう9。
$ bundle exec rake test
ActionController::UrlGenerationError:
No route matches {:action=>"about", :controller=>"static_pages"}
このエラーメッセージによれば、「指定されたアクション/コントローラの組み合わせに一致するルーティングが見当たらない」とあります。つまりルーティングファイルを修正する必要があるということです。リスト3.5のときと同じ要領で変更を行った結果をリスト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のままです。しかし今度はメッセージが少し変わりました。
$ bundle exec rake test
AbstractController::ActionNotFound:
The action 'about' could not be found for StaticPagesController
このエラーメッセージから、「StaticPagesコントローラにabout
アクションがない」ということがわかります。リスト3.6のhome
やhelp
と同じようにaboutアクションを追加します (リスト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のとおりにコードを入力します。
<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になるはずです。
$ bundle exec rake test
3 tests, 3 assertions, 0 failures, 0 errors, 0 skips
もちろん、実際にブラウザを起動して、テストが正しく動いているかどうかを確かめることもできます (図3.5)。
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.4.1 タイトルをテストする (Red)
ページタイトルを追加するために、典型的なWebページの構造を今一度おさらいしておきましょう (リスト3.21)。
<!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)。
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になります。
$ 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のようになります。
<!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>
Helpページ (リスト3.10) やAboutページ (リスト3.19) についても、同じ要領でリスト3.25 や リスト3.26のようなコードに変更します。
<!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>
<!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になるはずです。
$ 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"という文字を置き換え、動作を確認しましょう。
<% 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になるはずです。
$ bundle exec rake test
3 tests, 6 assertions, 0 failures, 0 errors, 0 skips
続いて、HelpページとAboutページも同様に変更します (リスト3.30、リスト3.31)。
<% 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>
<% 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のようになります。
<!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のように実に簡潔で美しいコードになります。
<% 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>
<% 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>
<% 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です。
$ bundle exec rake test
3 tests, 6 assertions, 0 failures, 0 errors, 0 skips
もちろん厳密に言えば、テストがパスしたというだけではそのコードが本当に正しいのかどうかの証明にはなりません。しかし正しいコードに確実に近づくことができ、正しい可能性も上がります。何よりも、テストがあれば今後発生するバグを防ぐためのセーフティネットになります。
3.4.4 ルーティングの設定
サイトのページのカスタマイズが終わって、テストスイートも軌道に乗ってきたので、今のうちにアプリケーションルートのルーティングを設定しておきましょう。1.3.4と2.2.2でやったように、ルーティングを設定するにはroutes.rb
ファイルを編集して、ルート「/」とWebページを結び付けます。結び付ける相手はHomeページです (3.1でApplicationコントローラにhello
アクションを追加した場合は、今のうちにアクションを削除しておくことをおすすめします)。変更結果をリスト3.37に示します。ここでは、リスト3.5のget
ルールを以下のコードに置き換えています。
root 'static_pages#home'
上のルールに置き換えると、static_pages/home
というURLからstatic_pages#home
というコントローラ/アクションへの対応付けが変わり、ルート「/」へのGETリクエストがStaticPagesコントローラのhome
アクションにルーティングされます。変更後のルーティングファイルを図3.7に示します。リスト3.37のコードにすると、static_pages/home
にアクセスしても動作しなくなります。
Rails.application.routes.draw do
root 'static_pages#home'
get 'static_pages/help'
get 'static_pages/about'
end
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にマージしていません)。今後の章では、ブランチやコミットメッセージはもちろん異なりますが、基本的なアイディアは同じです。
- StaticPagesコントローラのテスト (リスト3.22) にも重複があることにお気付きでしょうか。特に「Ruby on Rails Tutorial Sample App」を全てのタイトルテストでそのまま使っています。専用の
setup
関数 (テストの設定用関数、個別のテストの前に必ず毎回実行される) を使用してこの重複を解消し、テスト修正後もリスト3.38のテストがGREENになることを確認します (なお、リスト3.38ではインスタンス変数 (2.2.2と4.4.5) と文字列の式展開 (4.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のような修正は行われていないので、そのままコピペしても動きません)。
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
<% 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のデフォルトのテストで、必要に応じてREDやGREENを表示するためには、リスト3.40のコードをテスト用ヘルパーファイルに追加するだけです18。このコードでは、リスト3.2で追加したminitest-reporters gemを利用しています。
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)。
3.7.2 Backtrace silencer
テストが失敗した時に、テスト失敗の道筋をアプリケーション全体にわたってたどるスタックトレース (バックトレース) が表示されます。バックトレースは問題を追跡するうえでは非常に便利なのですが、クラウドIDEなど一部のシステムでは、このトレースがgemの依存関係やRails自身にまで及ぶことがあります。そうなると大量のスタックトレースが出力されて非常に不便です。gemの依存関係を調べているのでもなければ、開発しているアプリケーションで問題の原因を追跡中に大量のメッセージが出力されても、邪魔なだけです。
こうした不要な出力行を除去するために、バックトレースをフィルタします。これを行うにはmini_backtrace gem (リスト3.2) とbacktrace silencerを組み合わせます。クラウドIDEの場合、そうした不要な行ではほとんどの場合rvm
(=Ruby Version Manager) という文字がパスに含まれているので、これを利用してフィルタします (リスト3.41)。
# 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.2のGemfile
でguard 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をコピペしてしまった方がよいでしょう)
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では以下の操作を行います。
- ナビゲーションパネルの右上のにある歯車アイコンをクリックします (図3.9)。
- [Show hidden files] を選択して、アプリケーションのルートディレクトリにある
.gitignore
ファイルを表示します (図3.10). -
.gitignore
ファイル (図3.11) をダブルクリックして開き、リスト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)。
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 README、Guard wikiを参照してください。
- クラウドIDEをお使いの場合は「Goto Anything」コマンド (ファイル名の一部を入力するだけでその場所にジャンプする) が重宝します。「hello」アプリ「toy」アプリ「sample」アプリには同じファイル名が多数あるため、これらのアプリを同じプロジェクトに置くとファイルを見つけにくくなることがあります。たとえば「Gemfile」というファイル名を検索すると、
Gemfile
とGemfile.lock
を含め、候補が6つも表示されてしまいます。そこで、この先に進む前に先の2つのアプリを思い切って削除しておくとよいでしょう。アプリを削除するには、workspace
ディレクトリに移動してrm -rf hello_app/ toy_app/
コマンドを実行します(表1.1)。これらのアプリを既にBitbucketのリポジトリにプッシュしてあるなら、それを利用していつでもアプリを復元できます (その必要があればですが)。↑ - この--without productionオプションを一度実行すると「記憶される」ことにご注意ください。つまり、次回以降
bundle install
を実行すると、このオプションが暗黙で自動適用されます。↑ - 最終的には皆さんがPostgreSQLをdevelopment環境にインストールして設定できるようになるのが理想ですが、今は時期尚早であると考えます。実際に必要が生じたときは「install configure postgresql <自分のシステム>」や「rails postgresql setup」でググって各自挑戦してみてください (クラウドIDEの場合は<自分のシステム>にUbuntuと指定します)。↑
- 第2章でも指摘したとおり、主な理由は、デフォルトのRailsページはHerokuで破損してしまうことが多く、そのままだとデプロイが成功したのか失敗したのかがわかりにくいためです。↑
- ここで静的なページを作るために採用した方法は、おそらく最もシンプルな方法です。ただし他にも方法はあります。最適な方法は状況によって異なり、たとえば極めて多数の静的なページを1つのStaticPagesコントローラだけまかなおうとすると重荷になる可能性があります。今回はいくつかの静的なページを作るだけなので、重荷にはなりません。もし多数の静的なページが必要になる場合は、high_voltage gem を調べてみてください。なお、この問題には (やや古いですが) 有益な議論があります。 詳しくは hasmanythroughに投稿された記事「simple pages」 (英語) を読んでみてください。↑
- Rails生みの親であるDavid Heinemeier Hansson (通称DHH) の有名な記事『TDDは死んだ。テスティングに栄光あれ』(英語) を参照。↑
-
2.2でも説明したように、システム環境によっては
bundle exec
が追加不要なこともあります。クラウドIDE (1.2.1) も追加不要なシステムのひとつです。しかしここでは省略せずにコマンドを記述しています。著者の場合、原則としてbundle exec
は追加せずに実行し、うまくいかないときだけbundle exec
を追加して様子を見る、ということをよく行っています。↑ -
rake test
はデフォルトで、テストの失敗を赤色で表示しますが、テストがパスしても緑色で表示しません。色もちゃんと表示したい場合は3.7.1をご覧ください。↑ - システムによっては、ソースコードのエラーパスを追跡する「スタックトレース」または「バックトレース」と呼ばれるメッセージが大量に表示されることがあります。この場合、かなり上にスクロールする必要があるかもしれません。バックトレース出力を絞り込んで不要な行が表示されないようにしたい場合は、3.7.2をご覧ください。↑
- HTMLの仕様は時とともに変わる可能性があると思っておく方がよいでしょう。今後もブラウザでなるべく正しくページを表示できるように、doctypeを明示的に宣言しています。追加属性がまったくないシンプルな「
<!DOCTYPE html>
」は、最新標準であるHTML5の特徴です。↑ - minitestで利用できるアサーションのリストについては、Railsガイドの「Rails テスティングガイド」をご覧ください。↑
- 本書のスクリーンショットでは原則としてGoogle Chromeを使用していますが、Chromeのタブはタイトルを表示しきれないので、図3.6では代わりにSafariを使用しています。↑
- 2番目に人気のテンプレートとしてHamlがあり (注意: "HAML"ではありません) 、筆者は個人的にHamlの方が気に入っています。残念ながら十分に普及していないため、初級者向けチュートリアルの採用は見送りました。↑
- Railsでの開発経験者であれば、この時点で
content_for
の使用を検討すると思いますが、残念ながらAsset Pipelineと併用すると正常に動作しないことがあります。provide
関数はcontent_forの代替です。↑ - Rubyを勉強したことのある方であれば、Railsはブロックの内容をyieldしていると推測することでしょう。そして、その推測はおそらく正しいでしょう。しかし、Rails開発のためにこれらの詳細を知る必要はありません。↑
- コミット時に「マージするとSpringのプロセスID (pid) ファイルが上書きされる可能性があります」のようなエラーメッセージが表示される場合は、コマンドラインで
rm -f *.pid
を実行してpidファイルを削除してください。↑ - この演習は5.3.1の節に解答があります。↑
-
リスト3.40のコードには、シングルクオーテーション (') とダブルクオーテーション (") の両方が含まれています。これは、
rails new
で生成されたコードはシングルクオーテーションを使っていますが、minitestレポーターのドキュメントではダブルクオーテーションを使っていることが原因です。Rubyでは、この2つのクオーテーションを併用することが一般的です。詳しくは4.2.2で解説します。 ↑
Railsチュートリアルは YassLab 社によって運営されています。
コンテンツを継続的に提供するため、書籍・動画・質問対応サービスなどもご検討していただけると嬉しいです。
研修支援や教材連携にも対応しています。note マガジンや YouTube チャンネルも始めたので、よければぜひ遊びに来てください!