Ruby on Rails チュートリアル
-
第6版 目次
- 第1章ゼロからデプロイまで
- 第2章Toyアプリケーション
- 第3章ほぼ静的なページの作成
- 第4章Rails風味のRuby
- 第5章レイアウトを作成する
- 第6章ユーザーのモデルを作成する
- 第7章ユーザー登録
- 第8章基本的なログイン機構
- 第9章発展的なログイン機構
- 第10章ユーザーの更新・表示・削除
- 第11章アカウントの有効化
- 第12章パスワードの再設定
- 第13章ユーザーのマイクロポスト
- 第14章ユーザーをフォローする
|
||
第6版 目次
|
||
最新版を読む |
Ruby on Rails チュートリアル
プロダクト開発の0→1を学ぼう
下記フォームからメールアドレスを入力していただくと、招待リンクが記載されたメールが届きます。リンクをクリックし、アカウントを有効化した時点から『30分間』解説動画のお試し視聴ができます。
メール内のリンクから視聴を開始できます。
第6版 目次
- 第1章ゼロからデプロイまで
- 第2章Toyアプリケーション
- 第3章ほぼ静的なページの作成
- 第4章Rails風味のRuby
- 第5章レイアウトを作成する
- 第6章ユーザーのモデルを作成する
- 第7章ユーザー登録
- 第8章基本的なログイン機構
- 第9章発展的なログイン機構
- 第10章ユーザーの更新・表示・削除
- 第11章アカウントの有効化
- 第12章パスワードの再設定
- 第13章ユーザーのマイクロポスト
- 第14章ユーザーをフォローする
第3章ほぼ静的なページの作成
本章から、本格的なサンプルアプリケーションの開発を進めていきます。残りのチュートリアルでは、このアプリケーションを例題として扱って学習していくことになります。本書を通して開発するアプリケーションは、最終的にはユーザーやマイクロポスト、ログイン/ログアウトなどの認証機能を持ちますが、まずは簡単なトピックである「静的なページの作成」から始めます。非常に単純なページではありますが、静的なページを自分の手で作成することは良い経験になり、多くの示唆も得られます。私達がこれから開発するアプリケーションにとって最適なスタート地点といえるでしょう。
Railsはデータベースと連携して動的なWebサイトを開発するように設計されていますが、HTMLファイルだけで構成されている静的なページを作ることもできます。実際、Railsであえて静的なページを使っておき、後からほんの少し動的なコンテンツを追加することもできます。本章では、このような静的なページの作成について学んでいきます。本章ではそれと平行して、近年のプログラミングで不可欠となっている「自動化テスト」の雰囲気を掴んでいただきます。自動化テストを作成することで、コードが正しく動いていることが裏付けられます。さらに、良いテストを書くことで、自信をもってリファクタリングを行うことができます。例えばフォームの振る舞いを変更せずに、フォーム内で使われているメソッドを書き換えたいときに有用です。
3.1 セットアップ
第2章と同様に、新しいRailsプロジェクトを作成するところから始めます。今回はsample_app
という名前にします(リスト 3.1)1。
rails new
する前に容量を上げておくことをオススメします。 $ cd ~/environment
$ rails _6.0.4_ new sample_app
$ cd sample_app/
(2.1でも説明したとおり、クラウドIDEをご利用の方は、このプロジェクトをこれまでの2つの章で作成したプロジェクトと同じワークスペースに置くことができます。このプロジェクトで特に新しいワークスペースを作成する必要はありません。)
注: 本チュートリアルを全てやり遂げた時の完成版サンプルアプリケーションをGitHubに置いておきました2。もし途中でつまづいてしまったときは参考にしてみてください。
次に、2.1と同じように、テキストエディタを使ってGemfile
に必要なgemを書き足していきます。リスト 3.2は、リスト 1.8やリスト 2.1と基本的に同じですが、test
グループ内のgemだけが少し違っています。この変更でテスト用オプションを設定していますが、少し高度なので細かな解説は3.6に回します。今はあまり気にしないで大丈夫です。5.3.4.(注: もしサンプルアプリケーションの開発で必要になるgemをすべて知りたい場合は、リスト 13.76を参照してください。これが最終的なGemfileになります)。
Gemfile
Gemfile
source 'https://rubygems.org'
git_source(:github) { |repo| "https://github.com/#{repo}.git" }
ruby '2.7.6'
gem 'rails', '6.0.4'
gem 'puma', '4.3.6'
gem 'sass-rails', '5.1.0'
gem 'webpacker', '4.0.7'
gem 'turbolinks', '5.2.0'
gem 'jbuilder', '2.9.1'
gem 'bootsnap', '1.10.3', require: false
group :development, :test do
gem 'sqlite3', '1.4.2'
gem 'byebug', '11.0.1', platforms: [:mri, :mingw, :x64_mingw]
end
group :development do
gem 'web-console', '4.0.1'
gem 'listen', '3.1.5'
gem 'spring', '2.1.0'
gem 'spring-watcher-listen', '2.0.1'
end
group :test do
gem 'capybara', '3.28.0'
gem 'selenium-webdriver', '3.142.4'
gem 'webdrivers', '4.1.2'
gem 'rails-controller-testing', '1.0.4'
gem 'minitest', '5.11.3'
gem 'minitest-reporters', '1.3.8'
gem 'guard', '2.16.2'
gem 'guard-minitest', '2.4.6'
end
group :production do
gem 'pg', '1.1.4'
end
# Windows ではタイムゾーン情報用の tzinfo-data gem を含める必要があります
gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby]
前の2つの章と同様にbundle install
を実行して、Gemfile
で指定したgemのインストールをします。ただし、bundle config set --local without 'production'
オプションを使って、production環境でしか使わないgemはインストールしないようにしておきます3。
$ bundle _2.2.17_ config set --local without 'production'
$ bundle _2.2.17_ install
上のオプションを指定することで、PostgreSQL用のpg
gemをdevelopment環境にインストールせず、代わりにSQLiteがdevelopment環境とtest環境で使われるようになります。Herokuでは、development環境とproduction環境とで異なるデータベースを使うことを非推奨としていますが、幸いにもこのサンプルアプリケーションでは両者の違いは生じません。また、SQLiteの方がPostgreSQLよりもローカルでのインストールや設定がずっと楽なので、今回は異なるデータベースを使うことにします4。
なお、ネイティブのWindowsシステム上でRailsを実行する場合は、 1.3.1で行ったように、リスト 3.2の最終行のコメントアウトを解除することをお忘れなく。
もしGemfile
で指定されているのと異なるバージョンのgem(Rails自身のgemなど)をインストールしていた場合は、bundle update
を実行してgemを更新(update)し、gemのバージョンを合わせておくとよいでしょう。
$ bundle _2.2.17_ update
bundle install
(もしくはbundle update
)が完了したら、1.2.2でセットアップしたYarnを使ってWebpackerをインストールします。もしWebpackerを既にインストール済みの場合は設定を上書きするかどうかを聞かれるので、そのときは「no」と入力してください。
$ rails webpacker:install
ここまで進めたら、後はGitリポジトリを初期化するだけです。
$ git init
$ git add -A
$ git commit -m "Initialize repository"
最初のアプリケーションのときと同様に、まずはアプリケーションのルートディレクトリにあるREADME.md
ファイルを更新して、具体的な作業内容をわかりやすく記入しておくことをオススメします(リスト 3.3)。例えば次のように、READMEにはこのアプリケーションを使ってみる方法を記載してみましょう5(第6章まではrails db:migrate
が必要になることはありませんが、いずれ必要になるので今のうちに含めてしまいましょう)。
Note: 公式リポジトリのREADMEには、リスト 3.3には記載されていない追加情報も含めてありますので、ご活用ください。
README
README.md
# Ruby on Rails チュートリアルのサンプルアプリケーション
これは、次の教材で作られたサンプルアプリケーションです。
[*Ruby on Rails チュートリアル*](https://railstutorial.jp/)
(第6版)
[Michael Hartl](https://www.michaelhartl.com/) 著
## ライセンス
[Ruby on Rails チュートリアル](https://railstutorial.jp/)内にある
ソースコードはMITライセンスとBeerwareライセンスのもとで公開されています。
詳細は [LICENSE.md](LICENSE.md) をご覧ください。
## 使い方
このアプリケーションを動かす場合は、まずはリポジトリを手元にクローンしてください。
その後、次のコマンドで必要になる RubyGems をインストールします。
```
$ gem install bundler -v 2.2.17
$ bundle _2.2.17_ config set --local without 'production'
$ bundle _2.2.17_ install
```
その後、データベースへのマイグレーションを実行します。
```
$ rails db:migrate
```
最後に、テストを実行してうまく動いているかどうか確認してください。
```
$ rails test
```
テストが無事に通ったら、Railsサーバーを立ち上げる準備が整っているはずです。
```
$ rails server
```
詳しくは、[*Ruby on Rails チュートリアル*](https://railstutorial.jp/)
を参考にしてください。
書き終わったら、この変更をコミットします。
$ git commit -am "Improve the README"
1.4.4でgit commit -a -m "Message"
というGitコマンドを実行したことを思い出してください。あのときは “すべてを変更”(-a
)オプションとコミットメッセージを追加するオプション(-m
)を使いました。上で実行したコマンドで示したように、実はこれらの2つのオプションを1つにまとめてgit commit -am "Message"
と実行することができます。
1.4.3と同じ手順に沿ってGitHub上にリポジトリを作成し、リモートにもpushしておいてください(リポジトリは図 3.1のとおりprivateに設定しておくこと)。
$ git remote add origin https://github.com/<あなたのGitHubアカウント名>/sample_app.git
$ git push -u origin master
クラウドIDEをお使いの場合は、前の2つの章で行ったときと同様にdevelopment.rb
ファイルを編集し、アプリをローカルで起動できるようにする必要があります(リスト 3.4)。
config/environments/development.rb
Rails.application.configure do
.
.
.
# Cloud9 への接続を許可する
config.hosts.clear
end
後でproduction環境にプッシュするときに悩まずに済むよう、アプリをなるべく早い段階でHerokuにデプロイしておくとよいでしょう。第1章や第2章のときと同様に、リスト 3.5やリスト 3.6にある「hello, world」の手順に従って進めてみましょう。
(RailsのデフォルトのページはHeroku上ではうまく表示されない仕様になっています。このため、次のような変更を加えないとデプロイが成功したかどうかが分かりづらい、というのが本当の理由です。)
hello
アクションをApplicationコントローラーに追加する app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
def hello
render html: "hello, world!"
end
end
config/routes.rb
Rails.application.routes.draw do
root 'application#hello'
end
終わったら、次のように変更をコミットしてGitHubとHerokuにプッシュします6。
$ git commit -am "Add hello"
$ git push
$ heroku create
$ heroku stack:set heroku-20
$ git push heroku master
1.5のときと同じように警告メッセージが表示されることがありますが、無視して構いません。この警告は7.5で解決する予定です。これで、HerokuアプリのURL以外は、図 1.35のとおりに表示されるはずです。
この後も、本チュートリアルを進めながらアプリケーションをこまめにプッシュ/デプロイすることをオススメします。こうすることでリモートバックアップにもなり、production環境でのエラーを早めに確認することもできます。なお、Herokuに展開するときにエラーが発生した場合は、次のコマンドを実行して本番環境のログを取得してください。このログは、問題を特定するときに役立ちます。
$ heroku logs # 直近のイベントを表示する
$ heroku logs --tail # イベント発生のたびに自動表示する(Ctrl-Cで終了)
注: 今後Herokuで何らかの本番アプリケーションを運用する予定があるなら、7.5のproduction用Webサーバーの設定に必ず従ってください。
3.2 静的ページ
3.1の準備がすべて完了したら、いよいよサンプルアプリケーションの開発に取りかかりましょう。この節では、まずはRailsのアクションやビューを使って静的なHTMLのみのページを作成し、その後、静的なページを動的なページに作り変えていきます7。Railsのアクションは、コントローラ(1.3.3で説明したMVCの「C」)の中に置きます。また、コントローラ内の各アクションは目的に沿って互いに関連した操作(作成や削除など)を行います。コントローラについては第2章でも簡単に触れましたが、第6章で説明するRESTアーキテクチャを読むと理解が深まります。一言でまとめると、コントローラとは(基本的に動的な)Webページの集合を束ねるコンテナのことです。現在どのディレクトリで作業しているかがわからなくなった場合は、1.3(図 1.13)を再度参照して、Railsのディレクトリ構造を確認してください。この節では、主にapp/controllers
ディレクトリやapp/views
ディレクトリ内で作業を進めます
1.4.4で学んだことを思い出しましょう。Gitを使う場合は、masterブランチでずっと作業するのではなく、その都度トピックブランチを作成して作業するのがよい習慣です。Gitでバージョン管理を行っているのであれば、次のコマンドを実行して、静的なページ用のトピックブランチをチェックアウトしましょう。
$ git checkout -b static-pages
3.2.1 静的なページの生成
静的なページの作成は、第2章でscaffold生成に使った generate
スクリプトで、コントローラを生成することから始めます。このコントローラは静的なページを扱うためにしか使わないので、コントローラ名を「Static Pages」に決め、表記をキャメルケースのStaticPages
にします。続いて、Homeページ、Helpページ、Aboutページに使うアクションもそれぞれ作成することにし、アクション名はすべて小文字のhome
、help
、about
にします。generate
スクリプトではアクション名をまとめて指定することもできるので、コマンドラインでHomeページとHelpページ用のアクションもまとめて生成することにします。なお、Aboutページだけは学習のため、あえてコマンドラインでは作成せず、3.3で手動で追加することにします。これらの要素を盛り込んだStaticPagesコントローラ生成コマンドと実行結果をリスト 3.7に示します。
$ 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
invoke assets
invoke coffee
create app/assets/javascripts/static_pages.coffee
invoke scss
create app/assets/stylesheets/static_pages.scss
追伸: rails g
はrails generate
コマンドの短縮形であり、Railsでサポートされている多数の短縮形の1つです(表 3.1)。本チュートリアルではわかりやすさを重んじているため、こうしたコマンドは短縮せずに表記していますが、現実のRails開発者はほぼ間違いなく表 3.1の短縮形を常用しています8。
完全なコマンド | 短縮形 |
$ rails server |
$ rails s |
$ rails console |
$ rails c |
$ rails generate |
$ rails g |
$ rails test |
$ rails t |
$ bundle install |
$ bundle |
次に進む前に、StaticPagesコントローラファイルをGitリポジトリに追加しておきましょう。
$ git add -A
$ git commit -m "Add a Static Pages controller"
$ git push -u origin static-pages
最後のコマンドでは、static-pages
トピックブランチをGitHubにプッシュしています。以後は、単に次のコマンドを実行するだけで同じプッシュが行われるようになります。
$ git push
上のコミット〜プッシュの流れは、著者が実際の開発でよく使っていたパターンに基づいていますが、ここから先は途中でこのような指示をいちいち書くことはしませんので、各自こまめにプッシュするようにしてください(つまり本チュートリアルを進めるときは、セクションが終わるたびにGitにコミットしておくのがよい方法です)。
リスト 3.7では、コントローラ名をキャメルケース(単語の頭文字を大文字にしてつなぎ合わせた名前)で渡していることに注目してください。こうすると、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.7のように、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章で説明しますが、簡単に紹介すると、まずdb:migrate
でデータベースのマイグレーションを変更します。
$ rails db:migrate
元に戻したいときは、db:rollback
で1つ前の状態に戻します。
$ rails db:rollback
最初の状態に戻したいときは、VERSION=0
オプションを使います。
$ rails db:migrate VERSION=0
既にお気付きの方もいると思いますが、マイグレーションは逐次的に実行され、それぞれのマイグレーションに対してバージョン番号が付与されます。したがって、上記の0
を別の数字に置き換えることによって、指定したバージョンの状態に戻すことができます。
開発中に袋小路に迷い込んでしまった場合でも、これらの機能を使えば元の状態を復元できます。
リスト 3.7のようにStaticPagesコントローラを生成すると、(config/routes.rb
)ファイルが自動的に更新されます(1.3.4のときに、リスト 1.13のようにhelloアプリのルートルーティングを編集するとリスト 3.6のように直前の結果が表示されたのと同様です)。このルーティングファイルはルーターの実装を受け持ち(図 2.11)、URLとWebページの対応関係を定義します。このルーティングファイルはRailsのconfig
ディレクトリの下に置かれます。このディレクトリには、Railsの設定ファイルがまとめて置かれます(図 3.3)。
先ほどリスト 3.7のようにhome
アクションと help
アクションを生成したので、routesファイルにはそれぞれのアクションで使われるルールが定義されています(リスト 3.8)。
home
アクションとhelp
アクションで使うルーティング config/routes.rb
Rails.application.routes.draw do
get 'static_pages/home'
get 'static_pages/help'
root 'application#hello'
end
ここで次のルールに注目してみましょう。
get 'static_pages/home'
このルールは、/static_pages/homeというURLに対するリクエストを、StaticPagesコントローラのhome
アクションと結びつけています。今回はget
と書かれているため、GET
リクエストを受け取ったときに対応するアクションを結びつけています。なお、ここでいうGETリクエストとは、HTTP(HyperText Transfer Protocol) が対応しているメソッドの1つです(コラム 3.2)。
今回の場合は、StaticPagesコントローラ内にhome
アクションを追加したので、/static_pages/homeにアクセスすることでページを取得(GET)できるようになりました。結果を確認するには、1.3.2に従って次のようにRailsのdevelopmentサーバーを起動します。
$ rails server
Railsサーバーが立ち上がったら、/static_pages/homeにアクセスして結果を表示します(図 3.4)。
GET
やその他のHTTPメソッドについて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上のデータを読み取る(get)ときに使われます。「ページを取得する(get a page)」という意味のとおり、ブラウザはhttps://www.google.com/やhttps://www.wikipedia.org/などのWebサイトを開くたびにGET
リクエストをサイトに送信します。POST
は、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.9のような内容になっているはずです。ここで、第2章のUsersコントローラやMicropostsコントローラとは異なり、StaticPagesコントローラは一般的なRESTアクションに対応していないことに注意してください。これは、静的なページの集合に対しては、適切なアクションと言えます。言い換えると、RESTアーキテクチャは、あらゆる問題に対して最適な解決方法であるとは限らないということです。
app/controllers/static_pages_controller.rb
class StaticPagesController < ApplicationController
def home
end
def help
end
end
リスト 3.9の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.7をもう一度注意深く読んでみると、アクションとビューの関係について推測がつくと思いますが、home
アクションはhome.html.erb
というビューに対応しています。.erb
の詳細については3.4で説明しますが、ファイル名に.html
が含まれていることからわかるように、基本的にはHTMLと同じような構造になっています(3.10)。
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.11)。
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.10やリスト 3.11で見てきたように、重要なのは「Railsのビューの中には静的なHTMLがある」という点です。これは、Railsの知識が無くてもHomeページやHelpページを修正できることを意味しています。次のリスト 3.12とリスト 3.13がその一例です。
app/views/static_pages/home.html.erb
<h1>Sample App</h1>
<p>
This is the home page for the
<a href="https://railstutorial.jp/">Ruby on Rails Tutorial</a>
sample application.
</p>
app/views/static_pages/help.html.erb
<h1>Help</h1>
<p>
Get help on the Ruby on Rails Tutorial at the
<a href="https://railstutorial.jp/help">Rails Tutorial help page</a>.
To get help on this sample app, see the
<a href="https://railstutorial.jp/#ebook"><em>Ruby on Rails Tutorial</em>
book</a>.
</p>
リスト 3.12とリスト 3.13の結果をそれぞれ図 3.5と図 3.6に示します。
3.3 テストから始める
3.2.2でサンプルアプリのHomeページとHelpページを作成して中身も書き加えたので、今度はAboutページを同様に追加します。何らかの変更を行う際には、常に「自動化テスト」を作成して、機能が正しく実装されたことを確認する習慣をぜひ身に付けましょう。アプリケーションを開発しながらテストスイート(Test Suite) をみっちり作成しておけば、いざというときのセーフティネットにもなり、それ自体がアプリケーションのソースコードの「実行可能なドキュメント」にもなります。テストを作成するということは、その分コードを余分に書くことになりますが、正しく行えば、むしろテストがないときよりも確実に開発速度がアップします。テストが揃っていれば、バグを追うために余分な時間を使わずに済むためです。そんなふうにうまくいくとは信じられない人もいるかもしれませんが、一度でもテスト作成が上達すれば間違いなくこのとおりになります。だからこそ、テスト作成の習慣をできるだけ早いうちに身につけることが重要なのです。
テストが重要であるという点ではRails開発者の意見はほぼ一致していますが、細かい点では異論が生じているのも確かです。特に、テスト駆動開発(TDD)9(テストの手法の1つ: 最初に「正しいコードがないと失敗するテスト」を書き、次に本編のコードを書いてそのテストがパスするようにする)の是非については、当分議論が終わりそうにありません。筆者も悩んだ末、Ruby on Rails チュートリアルではこの点について「とにかく〜すべし」的な原理主義を避けることにしました。テストに関しては、原則として手軽かつ直感的なアプローチを採用し、必要に応じてTDDに切り替えるようにしています(コラム 3.3)。
それではいつ、どんなふうにテストを行えばよいのでしょうか。この点を理解するために、テストを行う目的をもう一度確認してみましょう。著者は、テストには次の3つのメリットがあると考えます。
- テストが揃っていれば、機能停止に陥るような回帰バグ(Regression Bug: 以前のバグが再発したり機能の追加/変更に副作用が生じたりすること)を防止できる。
- テストが揃っていれば、コードを安全にリファクタリング(機能を変更せずにコードを改善)できる。
- テストコードは、アプリケーションコードから見ればクライアントとして動作するので、アプリケーションの設計やシステムの他の部分とのインターフェイスを決めるときにも役に立つ。
上の3つのメリットは、テストを先に書かなくても得ることができますが、それでもテスト駆動開発(TDD)という手法をいつでも使えるようにしておけば、間違いなく多くの場面で役に立ちます。テストの手法やタイミングは、ある意味テストをどのぐらいすらすら書けるかで決まると言ってよいでしょう。たいていの開発者は、テストを書くのに慣れてくるとテストを先に書くようになります。その他にも、アプリケーションのコードと比べてテストがどのぐらい書きにくいか、必要な機能をどのぐらい正確に把握しているか、その機能が将来廃止される可能性がどのぐらいあるかによっても異なってくるでしょう。
こういうときのために、「テスト駆動」にするか「一括テスト」にするかを決める目安となるガイドラインがあると便利です。著者の経験を元に、次のようにまとめてみました。
- アプリケーションのコードよりも明らかにテストコードの方が短くシンプルになる(=簡単に書ける)のであれば、「先に」書く
- 動作の仕様がまだ固まりきっていない場合、アプリケーションのコードを先に書き、期待する動作を「後で」書く
- セキュリティが重要な課題またはセキュリティ周りのエラーが発生した場合、テストを「先に」書く
- バグを見つけたら、そのバグを再現するテストを「先に」書き、回帰バグを防ぐ体制を整えてから修正に取りかかる
- すぐにまた変更しそうなコード(HTML構造の細部など)に対するテストは「後で」書く
- リファクタリングするときは「先に」テストを書く。特に、エラーを起こしそうなコードや止まってしまいそうなコードを集中的にテストする
上のガイドラインに従う場合、現実には最初にコントローラやモデルのテストを書き、続いて統合テスト(モデル/ビュー/コントローラにまたがる機能テスト)を書く、ということになります。また、不安定な要素が特に見当たらないアプリケーションや、(主にビューが)頻繁に改定される可能性の高いアプリケーションのコードを書くときには、思い切ってテストを省略してしまうこともあります。
本書における主要なテストは、コントローラテスト(この節より)、モデルテスト(第6章より)、統合テスト(第7章より)の3つです。統合テストでは、ユーザーがWebブラウザでアプリケーションとやりとりする操作をシミュレートできるので特に強力です。統合テストはテストにおける最も主要な武器の1つとなりますが、まずは取っ付きやすいコントローラテストから始めることにしましょう。
3.3.1 最初のテスト
それではサンプルアプリケーションのAboutページの作成に取りかかります。やってみるとわかりますが、このページでは大したことは行わないので、テストは驚くほど短く単純です。早速コラム 3.3のガイドラインに沿って、テストを先に書くことにしましょう。続いてそのテストを実行して「失敗」することを確認し、実際のアプリケーションコードを書きます。
初めて書くテストがいきなり「テスト先行」というのは、Ruby on Railsの知識がある程度以上必要なため、少々敷居が高い面もあります。今の段階でテストを書かせようとすると、尻込みしてしまう人もいるかもしれません。しかしご心配なく。面倒な部分は既にRailsが全部面倒を見てくれています。rails generate controller
(リスト 3.7)を実行した時点でテストファイルがちゃんと作成されているので、それを利用しましょう。
$ ls test/controllers/
static_pages_controller_test.rb
生成されたテストを見てみましょう(リスト 3.14)。
test/controllers/static_pages_controller_test.rb
require 'test_helper'
class StaticPagesControllerTest < ActionDispatch::IntegrationTest
test "should get home" do
get static_pages_home_url
assert_response :success
end
test "should get help" do
get static_pages_help_url
assert_response :success
end
end
現時点では、上のリスト 3.14の文法をいきなり理解する必要はありません。今は「このファイルにはテストが2つ書かれている」ことを認識していただければ十分です。その2つのテストは、リスト 3.7で生成したコントローラの2つのアクションであるHomeとHelpに対応して生成されたものです。それぞれのテストでは、アクションをgetして正常に動作することを確認します。この確認は「アサーション」(assertion: 主張、断言)と呼ばれる手法で行います。get
は、HomeページやHelpページがいわゆる「GET
リクエストを受け付ける」普通のWebページであるということを示します(コラム 3.2)。その次の「response:success
」は、実際にはHTTPのステータスコード(ここでは200 OK)を表します。つまり、次のテストは
test "should get home" do
get static_pages_home_url
assert_response :success
end
言葉で表すと「Homeページのテスト。GET
リクエストをhome
アクションに対して発行(=送信)せよ。そうすれば、リクエストに対するレスポンスは[成功]になるはず」となります。
テストを書くサイクルに入る前に、まずは現在のテストスイートをそのまま実行して、問題なくパスすることを確認しておきましょう。テストの実行には次のようにrails
コマンドを使います。
$ rails db:migrate # システムによっては必要
$ rails test
2 tests, 2 assertions, 0 failures, 0 errors, 0 skips
テストスイートは期待どおり成功( green )します(システムによっては、成功時に色を表示させるためには、3.6.1で説明するminitest reportersを追加する必要がありますが、単に見やすくするためのものなので必須ではありません)。この箇所も含めて、本チュートリアルでは重要な部分に注目しやすくするため、テストの出力結果の一部を省略しています。
3.3.2 Red
コラム 3.3で解説したように、テスト駆動開発のサイクルは「失敗するテストを最初に書く」「次にアプリケーションのコードを書いて成功させる(パスさせる)」「必要ならリファクタリングする」のように進みます。多くのテストツールでは、テストの失敗を red 、成功したときを green で表します。ここから、このサイクルを「 red ・ green ・ REFACTOR」と呼ぶこともあります。これに従って最初のサイクルを完了させましょう。まず失敗するテストを書いて red になるようにします。テストを green にするのは3.3.3、リファクタリングは3.4.3で行います10。
サイクルの記念すべき第一歩はAboutページ用の失敗するテストを書くことです。リスト 3.14を参考にすれば、正しいテストコードを何となく想像できると思います。正しいテストコードをリスト 3.16に示します。
test/controllers/static_pages_controller_test.rb
require 'test_helper'
class StaticPagesControllerTest < ActionDispatch::IntegrationTest
test "should get home" do
get static_pages_home_url
assert_response :success
end
test "should get help" do
get static_pages_help_url
assert_response :success
end
test "should get about" do
get static_pages_about_url
assert_response :success
end
end
リスト 3.16のハイライト行を見ると、他のHomeページ用テストやHelpページ用テストとほとんど同じであることがわかります。違いは「home」や「help」の部分が「about」に変わっている点だけです。
テストを実行すると、期待どおり失敗します。
$ rails test
3 tests, 2 assertions, 0 failures, 1 errors, 0 skips
3.3.3 Green
テストがめでたく失敗した( red )ので、今度はこのテストのエラーメッセージを頼りにテストがパスする( green )ようにコードを書くことで、Aboutページを実装します。
失敗したテストのエラーメッセージをもっと詳しく見ていきましょう。
$ rails test
NameError: undefined local variable or method `static_pages_about_url'
このエラーメッセージによれば、「AboutページへのURLが見つからない」とあります。このメッセージをヒントに、ルーティングファイルを修正してみましょう。リスト 3.8のときと同じ要領で、変更を行った結果をリスト 3.19に示します。
about
用のルートを追加する red config/routes.rb
Rails.application.routes.draw do
get 'static_pages/home'
get 'static_pages/help'
get 'static_pages/about'
root 'application#hello'
end
リスト 3.19のハイライト行では、/static_pages/aboutというURLに対してGET
リクエストが来たら、StaticPagesコントローラのabout
アクションに渡すようRailsに指示しています。この結果、自動的に次のようなヘルパーが使えるようになります。
static_pages_about_url
修正が終わったらテストスイートをもう一度実行してみましょう。まだ red のままです。しかし今度はメッセージが少し変わりました。
$ rails test
AbstractController::ActionNotFound:
The action 'about' could not be found for StaticPagesController
このエラーメッセージから、「StaticPagesコントローラにabout
アクションがない」ということがわかります。リスト 3.9のhome
やhelp
と同じようにaboutアクションを追加します(リスト 3.21)。
about
アクションが追加されたStaticPagesコントローラ red app/controllers/static_pages_controller.rb
class StaticPagesController < ApplicationController
def home
end
def help
end
def about
end
end
今度はどうでしょう。まだ red ですが、エラーメッセージがまた少し変わりました。
$ rails test
ActionController::UnknownFormat: StaticPagesController#about
is missing a template for this request format and variant.
今度はテンプレートがないようです。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のようにファイルツリーを更新する必要があります)。こういったテクニックを少しずつ蓄えていくことが「熟練」への道です(コラム 1.2)。
さて、適切なディレクトリにabout.html.erb
ファイルを作成したら、リスト 3.22のとおりに内容を書き換えます。
app/views/static_pages/about.html.erb
<h1>About</h1>
<p>
<a href="https://railstutorial.jp/">Ruby on Rails Tutorial</a>
is a <a href="https://railstutorial.jp/#ebook">book</a> and
<a href="https://railstutorial.jp/screencast">screencast</a>
to teach web development with
<a href="https://rubyonrails.org/">Ruby on Rails</a>.
This is the sample application for the tutorial.
</p>
今度のrails test
の結果は green になるはずです。
$ rails test
3 tests, 3 assertions, 0 failures, 0 errors, 0 skips
もちろん、実際にブラウザを起動して、テストが正しく動いているかどうかを確かめることもできます(図 3.7)。
3.3.4 Refactor
テストが green になったので、安心してコードをリファクタリングできるようになりました。アプリケーションの開発が進むと、コードのどこからともなく「腐敗臭」が漂い始めます。コードや記法の統一が崩れて読みづらくなる、クラスやメソッドが何百行にも膨れ上がって読む気を削がれる、なぜこのコードがここにあるのか最早誰もその理由を思い出せなくなる、同じコードがあちこちにコピペされて少しずつ書き換えられ手に負えなくなる、などです。コンピュータにしてみればどんなに汚らしいコードであろうと、そこにあるがままに実行するだけですが、人間はそういうわけにはいきません。こまめにリファクタリングを繰り返してコードを常にすみずみまで美しくコンパクトに保ち、他の開発者や未来の自分の開発意欲を阻喪することのないようにしなければなりません。このサンプルアプリは生まれたてなので、今のところリファクタリングの必要な箇所はほぼどこにも見当たりません。しかし「一匹いれば30匹いると思え」、コードの腐敗臭はどんな小さな隙間からも忍び寄ってきます。こまめなリファクタリングの習慣をできるだけ早いうちに身につけるためにも、少々無理やりに3.4.3から始めることにします。
3.4 少しだけ動的なページ
静的なページのアクションやビューをいくつか作成できたので、今度はそれをほんの少しだけ動的にしてみましょう。ページの内容に応じて、ページのタイトルを自ら書き換えて表示するようにします。タイトルを自動で変えるぐらいのことが真の動的コンテンツと呼べるかどうかは議論の余地があると思いますが、いずれにしろこのページは、第7章で紹介する本格的な動的コンテンツの基礎となります。
ここでの目標は、Homeページ、Helpページ、Aboutページをそれぞれ編集し、最終的にページごとに異なるタイトルを表示することです。ここではビューの<title>
タグの内容を変更します。多くのブラウザでは、titleタグの内容をブラウザウィンドウの上部にウィンドウタイトルとして表示します。titleタグは、いわゆるSEO(Search Engine Optimization: 検索エンジン最適化)においても重要な役割を果たします。今度は「 red ・ green ・ REFACTOR」のサイクルをすべて行うことにします。ページタイトルの簡単なテストを書き( red )、3つのページにタイトルを追加し( green )、レイアウトファイルを活用してコードの重複を解決します(REFACTOR)。本節の終わりまでに、3つの静的ページのタイトルを「<ページ名> | Ruby on Rails Tutorial Sample App」という形式に変更します。「<ページ名>」の部分が、表示しているページに応じて動的に変わります(表 3.2)。
前述のrails new
コマンド(リスト 3.1)を実行すると、レイアウトもデフォルトで作成されます。ここでは学習のため、一時的に次のようにファイル名を変更します。
$ mv app/views/layouts/application.html.erb layout_file
普通は、実際のアプリケーション開発時に上のような操作を行うことはありません。ここでは、レイアウトファイルの役割をよりわかりやすく説明するために、最初にレイアウトファイルを無効にしています。
ページ | 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)
ページタイトルを追加する前に、HTMLについて今一度おさらいしておきましょう。典型的なWebページは、リスト 3.24のようなHTMLの構造を持っています(HTMLの詳細は 『HTML編』を参照)。
<!DOCTYPE html>
<html>
<head>
<title>Greeting</title>
</head>
<body>
<p>Hello, world!</p>
</body>
</html>
リスト 3.24の構造には次の3つが含まれています。
1)document type(doctype)は使用するHTMLのバージョン(ここではHTML5)をブラウザに対して宣言します11。
2)head
セクション。ここではtitle
タグに囲まれた「Greeting」(=あいさつ)という文字があります。
3)body
セクション。ここには「Hello, world!」という文字列があります。「Hello, world!」は、p
(paragraph)タグの中にあります(HTMLではスペースやタブが無視されるので、インデントはあってもなくても大丈夫です。とはいえインデントがあると、HTMLのデータ構造が理解しやすくなります)。
表 3.2の各タイトルについて簡単なテストを書きます(リスト 3.16)。このテストで使っているassert_select
メソッドでは、特定のHTMLタグが存在するかどうかをテストします(この種のアサーションメソッドはその名から「セレクタ」と呼ばれることもあります)12。
assert_select "title", "Home | Ruby on Rails Tutorial Sample App"
上のセレクタは、<title>
タグ内に「Home | Ruby on Rails Tutorial Sample App」という文字列があるかどうかをチェックします。同じ要領で3つの静的ページを書き換えます(リスト 3.25)。
test/controllers/static_pages_controller_test.rb
require 'test_helper'
class StaticPagesControllerTest < ActionDispatch::IntegrationTest
test "should get home" do
get static_pages_home_url
assert_response :success
assert_select "title", "Home | Ruby on Rails Tutorial Sample App"
end
test "should get help" do
get static_pages_help_url
assert_response :success
assert_select "title", "Help | Ruby on Rails Tutorial Sample App"
end
test "should get about" do
get static_pages_about_url
assert_response :success
assert_select "title", "About | Ruby on Rails Tutorial Sample App"
end
end
リスト 3.25どおりにテストを作成すると、テストスイートは red になります。
$ rails test
3 tests, 6 assertions, 3 failures, 0 errors, 0 skips
3.4.2 タイトルを追加する(Green)
今度は各ページにタイトルを追加して、3.4.1のテストがパスするようにしましょう。リスト 3.24の基本HTML構造をカスタムのHomeページ(リスト 3.12)に追加するとリスト 3.27のようになります。
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="https://railstutorial.jp/">Ruby on Rails Tutorial</a>
sample application.
</p>
</body>
</html>
このページの表示を図 3.8に示します。なお、本チュートリアルスクリーンショットでは使ってるブラウザ(Safari)は、Safariはタブが2つしかないとき、図 3.8のようにページタイトルのみを表示しています。
Helpページ(リスト 3.13)やAboutページ(リスト 3.22)についても、同じ要領でリスト 3.28 や リスト 3.29のようなコードに変更します。
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="https://railstutorial.jp/help">Rails Tutorial help
page</a>.
To get help on this sample app, see the
<a href="https://railstutorial.jp/#ebook">
<em>Ruby on Rails Tutorial</em> book</a>.
</p>
</body>
</html>
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>
<a href="https://railstutorial.jp/">Ruby on Rails Tutorial</a>
is a <a href="https://railstutorial.jp/#ebook">book</a> and
<a href="https://railstutorial.jp/screencast">screencast</a>
to teach web development with
<a href="https://rubyonrails.org/">Ruby on Rails</a>.
This is the sample application for the tutorial.
</p>
</body>
</html>
これでテストスイートは green になるはずです。
$ rails test
3 tests, 6 assertions, 0 failures, 0 errors, 0 skips
演習
本演習からサンプルアプリケーションを修正する課題を出していきますが、基本的には演習で修正した結果が今後の開発に影響を与えないよう配慮してあります。なぜこんなことを今説明しているのかというと、今後の演習を進めていくうちに、本書のソースコードと読者のソースコードが少しずつ変わっていく可能性があるからです。しかし、そういった差異から生じる問題を解決していくことは、「熟練」に至るまでの学びには欠かせません(コラム 1.2)。ぜひ次の演習にも果敢に取り組んでみてください。
- StaticPagesコントローラのテスト(リスト 3.25)には、いくつか繰り返しがあったことにお気づきでしょうか? 特に「Ruby on Rails Tutorial Sample App」という基本タイトルは、各テストで毎回同じ内容を書いてしまっています。そこで、
setup
という特別なメソッド(各テストが実行される直前で実行されるメソッド)を使って、この問題を解決したいと思います。まずは、リスト 3.31のテストが green になることを確認してみてください(リスト 3.31では、2.2.2で少し触れたインスタンス変数や文字列の式展開というテクニックを使っています。それぞれ4.4.5と4.2.1で詳しく解説するので、今はわからなくても問題ありません)。
test/controllers/static_pages_controller_test.rb
require 'test_helper'
class StaticPagesControllerTest < ActionDispatch::IntegrationTest
def setup
@base_title = "Ruby on Rails Tutorial Sample App"
end
test "should get home" do
get static_pages_home_url
assert_response :success
assert_select "title", "Home | #{@base_title}"
end
test "should get help" do
get static_pages_help_url
assert_response :success
assert_select "title", "Help | #{@base_title}"
end
test "should get about" do
get static_pages_about_url
assert_response :success
assert_select "title", "About | #{@base_title}"
end
end
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のテストを実行して、タイトルを壊していないことを確認します。
上の話と一見矛盾するようですが、最初にコードを若干追加して、現在は「ほぼ」同じになっているページのタイトルを「完全に」同じにしておきます。この方が、コードの重複を一括で取り除けるからです。
重複を取り除くテクニックの1つとして、ビューで「埋め込みRuby」(Embedded Ruby)が使えます。Home、Help、Aboutページには可変要素があるので、Railsのprovide
メソッドを使ってタイトルをページごとに変更します。それでは、home.html.erb
ビューのコード内のタイトルに含まれている "Home" という文字を置き換えて、動作を確認してみましょう(リスト 3.32)。
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="https://railstutorial.jp/">Ruby on Rails Tutorial</a>
sample application.
</p>
</body>
</html>
リスト 3.32は、ERB(または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 になるはずです。
$ rails test
3 tests, 6 assertions, 0 failures, 0 errors, 0 skips
続いて、HelpページとAboutページも同様に変更します(リスト 3.34、リスト 3.35)。
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="https://railstutorial.jp/help">Rails Tutorial help
section</a>.
To get help on this sample app, see the
<a href="https://railstutorial.jp/#ebook">
<em>Ruby on Rails Tutorial</em> book</a>.
</p>
</body>
</html>
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>
<a href="https://railstutorial.jp/">Ruby on Rails Tutorial</a>
is a <a href="https://railstutorial.jp/#ebook">book</a> and
<a href="https://railstutorial.jp/screencast">screencast</a>
to teach web development with
<a href="https://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.36のようになります。
app/views/layouts/application.html.erb
<!DOCTYPE html>
<html>
<head>
<title><%= yield(:title) %> | Ruby on Rails Tutorial Sample App</title>
<%= csrf_meta_tags %>
<%= csp_meta_tag %>
<%= stylesheet_link_tag 'application', media: 'all',
'data-turbolinks-track': 'reload' %>
<%= javascript_pack_tag 'application',
'data-turbolinks-track': 'reload' %>
</head>
<body>
<%= yield %>
</body>
</html>
上のコード内にある、次の特殊なコードにご注目ください。
<%= yield %>
このコードは、各ページの内容をレイアウトに挿入するためのものです。ここでは、このコードの詳細な動作を正確に理解することは重要ではありません。レイアウトを使う際に、/static_pages/homeにアクセスするとhome.html.erb
の内容がHTMLに変換され、<%= yield %>
の位置に挿入される、ということだけ理解しておけば問題ありません。
また、Railsのデフォルトのレイアウトでは、次の行が追加されていることにもご注目ください。
<%= csrf_meta_tags %>
<%= csp_meta_tag %>
<%= stylesheet_link_tag ... %>
<%= javascript_pack_tag "application", ... %>
上のERBは、スタイルシートとJavaScriptをページ内にインクルードする(=含める)ためのものです。スタイルシートとJavaScriptは、Asset Pipeline(5.2.1)の一部であり、クロスサイトスクリプティング攻撃を緩和するコンテンツセキュリティポリシー(Content Security Policy: CSP)を実装するcsp_meta_tag
と、クロスサイトリクエストフォージェリー(Cross-Site Request Forgery: CSRF)攻撃を緩和するcsrf_meta_tags
も含みます(こうしたセキュリティ対策を開発者が気にしなくて済む点は、Railsのような長年実績を積んでいるフレームワークを使う大きなメリットです)。
テストが無事パスしても、まだやることは残っています。リスト 3.32、リスト 3.34、 リスト 3.35のビューには、レイアウトと重複する不要なHTML構造がまだ残っていて、このままではHTMLマークアップが壊れてしまうので、この不要なHTML構造を削除して、内部のコンテンツだけ残します。この改修が終わると、 リスト 3.37、リスト 3.38、リスト 3.39のように実に簡潔で美しいコードになります。
app/views/static_pages/home.html.erb
<% provide(:title, "Home") %>
<h1>Sample App</h1>
<p>
This is the home page for the
<a href="https://railstutorial.jp/">Ruby on Rails Tutorial</a>
sample application.
</p>
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="https://railstutorial.jp/help">Rails Tutorial help page</a>.
To get help on this sample app, see the
<a href="https://railstutorial.jp/#ebook"><em>Ruby on Rails Tutorial</em>
book</a>.
</p>
app/views/static_pages/about.html.erb
<% provide(:title, "About") %>
<h1>About</h1>
<p>
<a href="https://railstutorial.jp/">Ruby on Rails Tutorial</a>
is a <a href="https://railstutorial.jp/#ebook">book</a> and
<a href="https://railstutorial.jp/screencast">screencast</a>
to teach web development with
<a href="https://rubyonrails.org/">Ruby on Rails</a>.
This is the sample application for the tutorial.
</p>
上のように定義されたビューは、Home、Help、Aboutページの表示は以前と変わりませんが、コードの重複が大きく削減されました。
この節で行ったようなちっぽけなリファクタリングですら、実際にやってみると大小さまざまなエラーが発生します。ベテラン開発者ほどこのことを骨の髄まで理解しており、どんな小さなリファクタリングでもあなどったりしません。テストスイートをきちんと整備しておくことがいかに重要であるか、皆さんにもご理解いただけると思います。開発のごく初期の段階なら全ページを目視で1つ1つ確認して回ることもできるかもしれませんが、そんな方法ではじきに手に負えなくなります。このアプリでは必要なテストスイートが整備されているので、今度も green になることを確認するだけでOKです。
$ rails test
3 tests, 6 assertions, 0 failures, 0 errors, 0 skips
もちろん厳密に言えば、テストがパスしたというだけではそのコードが本当に正しいのかどうかの証明にはなりません。しかし正しいコードに確実に近づくことができ、正しい可能性も上がります。何よりも、テストがあれば今後発生するバグを防ぐためのセーフティネットになります。
演習
- サンプルアプリケーションにContact(問い合わせ先)ページを作成してください16。(ヒント: まずはリスト 3.16を参考にして、/static_pages/contactというURLのページに「Contact | Ruby on Rails Tutorial Sample App」というタイトルが存在するかどうかを確認するテストを最初に作成しましょう。次に、3.3.3でAboutページを作ったときのと同じように、Contactページにもリスト 3.41のコンテンツを表示してみましょう。)
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="https://railstutorial.jp/contact">contact page</a>.
</p>
3.4.4 ルーティングの設定
サイトのページのカスタマイズが終わって、テストスイートも軌道に乗ってきたので、今のうちにアプリケーションルートのルーティングを設定しておきましょう。1.3.4と2.2.2でやったように、ルーティングを設定するにはroutes.rb
ファイルを編集して、ルート「/」とWebページを結び付けます。結び付ける相手はHomeページです(3.1でApplicationコントローラにhello
アクションを追加した場合は、今のうちにアクションを削除しておくことをオススメします)。変更結果であるリスト 3.42が示すように、ここではroot
ルールを、
root 'application#hello'
次のコードに置き換えています。
root 'static_pages#home'
上のルールに置き換えるとコントローラ/アクションへの対応付けが変わり、ルート「/」へのGETリクエストがStaticPagesコントローラのhome
アクションにルーティングされます。無事に変更できれば、図 3.9のようになるはずです。
config/routes.rb
Rails.application.routes.draw do
root 'static_pages#home'
get 'static_pages/home'
get 'static_pages/help'
get 'static_pages/about'
end
演習
- リスト 3.42にrootルーティングを追加したことで、
root_url
というRailsヘルパーが使えるようになりました(以前、static_pages_home_url
が使えるようになったときと同じです)。リスト 3.43の(コードを書き込む)
と記された部分を置き換えて、rootルーティングのテストを書いてみてください。 - 実はリスト 3.42のコードを書いていたので、先ほどの課題のテストは既に green になっているはずです。このような場合、テストを変更する前から成功していたのか、変更した後に成功するようになったのか、判断が難しいです。リスト 3.42のコードがテスト結果に影響を与えていることを確認するため、リスト 3.44のようにrootルーティングをコメントアウトして見て、 red になるかどうか確かめてみましょう(なおRubyのコメント機能については4.2で説明します)。最後に、コメントアウトした箇所を元に戻し(すなわちリスト 3.42に戻し)、テストが green になることを確認してみましょう。
test/controllers/static_pages_controller_test.rb
require 'test_helper'
class StaticPagesControllerTest < ActionDispatch::IntegrationTest
test "should get root" do
get (コードを書き込む)
assert_response (コードを書き込む)
end
test "should get home" do
get static_pages_home_url
assert_response :success
end
test "should get help" do
get static_pages_help_url
assert_response :success
end
test "should get about" do
get static_pages_about_url
assert_response :success
end
end
config/routes.rb
Rails.application.routes.draw do
# root 'static_pages#home'
get 'static_pages/home'
get 'static_pages/help'
get 'static_pages/about'
end
3.5 最後に
はたから見れば、皆さんがこの章で結局どんなことを達成できたのかさっぱりわからないかもしれません。静的なページをアレコレ作り変えて … 最終的にほぼ静的なページにしただけなのでしょうか。もちろんそんなことはありません。皆さんはこの章でRailsのコントローラ、アクション、ビューの開発をひととおり行ったことで、これから動的なコンテンツをどしどしサイトに追加するための準備がすっかり整ったのです。残る課題は、皆さんがこのチュートリアルをいかに最後までやりぬくか、それだけであると言ってよいでしょう。
次の章に進む前に、差分をコミットしてmasterブランチにマージしておきましょう。3.2では、静的ページの開発のためのGitブランチを用意しました。ここまでの作業内容をコミットしていない場合、作業の区切りをつけるためにもコミットしましょう。
$ git add -A
$ git commit -m "Finish static pages"
次にmasterブランチに移動し、1.4.4と同じ要領で差分をマージします17。
$ git checkout master
$ git merge static-pages
このようにきりのいいところまで達したら、コードをリモートリポジトリにアップロードしておくとよいでしょう(1.4.3の手順に従っていれば、リモートリポジトリにはGitHubが使われます)。
$ git push
また、この時点でHerokuにデプロイしてみてもよいでしょう。
$ rails test
$ git push heroku
デプロイする前にテストを走らせていますが、こういった習慣を身につけておくと開発に役立ちます。
3.5.1 本章のまとめ
- 新しいRailsアプリケーションをゼロから作成したのはこれで3度目。今回も必要なgemのインストール、リモートリポジトリへのプッシュ、production環境まで行った
- コントローラを新規作成するための
rails
コマンドはrails generate controller ControllerName アクション名(省略可)
。 - 新しいルーティングは
config/routes.rb
ファイルで定義する - Railsのビューでは、静的HTMLの他にERB(埋め込みRuby: Embedded RuBy)が使える
- 常に自動化テストを使って新機能開発を進めることで、自信を持ってリファクタリングできるようになり、回帰バグも素早くキャッチできるようになる
- テスト駆動開発では「 red ・ green ・REFACTOR」サイクルを繰り返す
- Railsのレイアウトでは、アプリケーションのページの共通部分をテンプレートに置くことでコードの重複を解決することができる
3.6 高度なセットアップ
この追加の節は、テスト用設定について解説します。大きく分けると、成功/失敗の表示設定をする「minitest reporters(3.6.1)」と、ファイルの変更を検出して必要なテストだけを自動実行してくれる「Guard(3.6.2)」の2つです。この節で参考までに示したコードはそれなりに高度なので、今すぐ理解できるようになる必要はありません。
なお、このセクションで紹介するセットアップはmasterブランチで行う必要があるので、今のうちにブランチをmasterに切り替えておきましょう。
$ git checkout master
3.6.1 minitest reporters
リスト 3.2にあるminitest-reporters
というgemを追加すると、クラウドIDEを含む多くのシステムで red や green の表示が見やすくなりますので、リスト 3.45のコードをみなさんのテストヘルパーファイルにも追加しておくことをオススメします18。
test/test_helper.rb
ENV['RAILS_ENV'] ||= 'test'
require_relative '../config/environment'
require 'rails/test_help'
require "minitest/reporters"
Minitest::Reporters.use!
class ActiveSupport::TestCase
# 特定のワーカーではテストをパラレル実行する
parallelize(workers: :number_of_processors)
# すべてのテストがアルファベット順に実行されるよう、
#test/fixtures/*.ymlにあるすべてのfixtureをセットアップする
fixtures :all
# (すべてのテストで使うその他のヘルパーメソッドは省略)
end
この変更により、クラウドIDE上の表示が red から green に変わります(図 3.10)。
3.6.2 Guardによるテストの自動化
rails test
コマンドを使うとき、テストをしようとする度にエディタからコマンドラインに移動して、手動でコマンドを打ち込み、実行しなければならないという点が面倒ではありませんでしたか。この不便さを取り除くために、Guardを使ってテストを自動的に実行させるようにしてみましょう。Guardは、ファイルシステムの変更を監視し、例えばstatic_pages_test.rb
ファイルなどを変更すると自動的にテストを実行してくれるツールです。具体的には、「home.html.erb
ファイルが変更されたらstatic_pages_controller_test.rb
を自動的に実行する」といったことをGuardで設定することができます。
実は既に、リスト 3.2のGemfile
でguard
gemをアプリケーション内に取り込んでいます。したがって、あとは初期化をするだけで動かすことができます。
$ bundle exec guard init
Writing new Guardfile to /home/ec2-user/environment/sample_app/Guardfile
00:51:32 - INFO - minitest guard added to Guardfile, feel free to edit it
統合テストとビューが更新されたら自動的に適切なテストが実行されるように、生成されたGuardfile
を編集します(リスト 3.46)。柔軟性を最大にするため、チュートリアルの参照用アプリケーションに記載されているのと同じバージョンのGuardfile
をお使いになることをオススメします。本チュートリアルをオンラインでお読みの方は、リスト 3.46で正確な書き方を見ることができます。
Guardfile
の参照: railstutorial.org/guardfile
Guardfile
Guardfile
# Guardのマッチング規則を定義
guard :minitest, spring: "bin/rails test", all_on_start: false do
watch(%r{^test/(.*)/?(.*)_test\.rb$})
watch('test/test_helper.rb') { 'test' }
watch('config/routes.rb') { interface_tests }
watch(%r{app/views/layouts/*}) { interface_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
# 与えられたリソースに対応する統合テストを返す
def integration_tests(resource = :all)
if resource == :all
Dir["test/integration/*"]
else
Dir["test/integration/#{resource}_*.rb"]
end
end
# インターフェースが該当するすべてのテストを返す
def interface_tests
integration_tests << "test/controllers/"
end
# 与えられたリソースに対応するコントローラのテストを返す
def controller_test(resource)
"test/controllers/#{resource}_controller_test.rb"
end
# 与えられたリソースに対応するすべてのテストを返す
def resource_tests(resource)
integration_tests(resource) << controller_test(resource)
end
クラウドIDEではもうひとつ手順が必要です。次の少々ややこしそうなコマンドを実行して、プロジェクト内の全ファイルをGuardで監視できるようにします。
$ echo fs.inotify.max_user_watches=524288 | sudo tee -a /etc/sysctl.conf
$ sudo sysctl -p
Guardの設定が完了したら、新しいターミナルを開き(1.3.2でやったようにRailsサーバーのターミナルと別にするのがポイントです)、次のコマンドを実行します(図 3.11)。
$ bundle exec guard
リスト 3.46のルールは本チュートリアルに最適化したものなので、例えばコントローラのファイルを変更すると、Guardは即座にそれを検出して、そのコントローラの統合テストを自動実行します。テストを変更ファイルだけではなく、フルで実行したい場合は、guard>
プロンプトでReturnキーを押します。
Guardを終了するにはCtrl-Dキーを押します。Guardに他のマッチャーを追加する方法については、リスト 3.46の例やGuard README、Guard wiki(英語)を参照してください。
はっきりした原因がないのにテストが失敗する場合は、次のようにGuardをいったん終了し、Spring(Railsの情報を事前読み込みしてテストを高速化するツール)を止めて再起動してみましょう。
$ bin/spring stop # テストが原因不明で動かなくなったら、このコマンドを実行してみる
$ bundle exec guard
これで高度なセットアップは終了です。次の章に進む前に、ここまでの変更を忘れずにコミットしておきましょう。
$ git add -A
$ git commit -m "Complete advanced testing setup"
Gemfile
やGemfile.lock
を含め、候補が6つも表示されてしまいます。そこで、この先に進む前に先の2つのアプリを思い切って削除しておくとよいでしょう。アプリを削除するには、environment
ディレクトリに移動してrm -rf hello_app/ toy_app/
コマンドを実行します(表 1.1)。これらのアプリを既にGitHubのリポジトリにプッシュしてあるなら、それを利用していつでもアプリを復元できます(その必要があればですが)。--local without 'production'
オプションを一度実行すると「記憶される」ことにご注意ください。つまり、次回以降bundle install
を実行すると、このオプションが暗黙的に適用されるようになります。high_voltage
gem を調べてみてください。r
と実行するとrails
コマンドが実行される、といった具合です。このエイリアスを設定していれば、$ r s
とコマンドを打つだけでRailsサーバーを立ち上げられるようになります。詳しくは 『テキストエディタ編』の「ファイルを保存してVimを終了する」を読んでみてください。rails test
でテストが失敗すると赤色で表示しますが、テストが成功しても緑色で表示しないことがあります。色もちゃんと表示したい場合は3.6.1をご覧ください。<!DOCTYPE html>
」は、最新標準であるHTML5の特徴です。content_for
を検討すると思いますが、残念ながらAsset Pipelineと併用すると正常に動作しないことがあります。provide
メソッドはcontent_forの代替です。rm -f *.pid
を実行してpidファイルを削除してください。rails new
で生成されたコードではシングルクオーテーションを使っていますが、minitest reportersのドキュメントではダブルクオーテーションを使っていることが原因です。Rubyでは、この2つのクオーテーションを併用することが一般的です。詳しくは4.2.1で解説します。
Railsチュートリアルは YassLab 社によって運営されています。
コンテンツを継続的に提供するため、書籍・動画・質問対応サービスなどもご検討していただけると嬉しいです。
研修支援や教材連携にも対応しています。note マガジンや YouTube チャンネルも始めたので、よければぜひ遊びに来てください!