Ruby on Rails チュートリアル

プロダクト開発の0→1を学ぼう

第7版 目次

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

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

Railsはデータベースと連携して動的なWebサイトを開発するように設計されていますが、HTMLファイルだけで構成されている静的なページを作ることもできます。実際、Railsであえて静的なページを使っておき、後からほんの少し動的なコンテンツを追加することもできます。本章では、このような静的なページの作成について学んでいきます。

本章ではそれと平行して、近年のプログラミングで不可欠となっている「自動化テスト」の雰囲気を掴んでいただきます。自動化テストを作成することで、コードが正しく動いていることが裏付けられます。さらに、良いテストを書くことで、自信をもってリファクタリングを行うことができます。例えばフォームの振る舞いを変更せずに、フォーム内で使われているメソッドを書き換えたいときに有用です。

3.1 セットアップ

2と同様に、新しいRailsプロジェクトを作成するところから始めます。今回はsample_appという名前にします(リスト 3.11。本チュートリアルを完了したときの完成版サンプルアプリケーションをGitHubに置いておきました。もし途中でつまづいてしまったときは参考にしてみてください2

Cloud9で開発していると容量が足りなくなることもあります。リスト 1.4を実行し、rails newする前に容量を増やしておくことをオススメします。
リスト 3.1: サンプルアプリケーションを生成する
$ cd ~/environment
$ rails _7.0.4_ new sample_app --skip-bundle
$ cd sample_app/

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

ところで、リスト 3.1--skip-bundleは、RailsでJavaScriptを利用するうえで必要なパッケージのインストールもあえてスキップしています。これらのパッケージは、この後の 8.2.4で手動インストールする予定なので、それまでは基本的にインストールしないでおくことをオススメします。

なお、JavaScriptをすぐに使いたい人向けに、利用可能なコマンドをリスト 8.20で参照できることを一応お知らせしておきます。ただし、これらのコマンドを今の時点で実行すると、本チュートリアルのコードと自分のコードに若干食い違いが生じます(application.html.erbレイアウトテンプレートにjavascript_importmap_tagsが含まれるなど)。そのときは、例の「熟練」(コラム 1.2)を発揮して違いを吸収してください。

次に、2.1と同じように、テキストエディタを使ってGemfileに必要なgemを書き足していきます。リスト 3.2は、リスト 2.1と基本的に同じですが、testグループ内のgemだけが少し違っています。この変更でテスト用オプションを設定していますが、少し高度なので細かな解説は 3.6と 5.3.4に回します。今はあまり気にしなくても大丈夫です。

サンプルアプリケーションの最終的なGemfileはリスト 13.80に載っています。開発で必要になるgemをすべて知りたい場合は参照してください。

電子書籍版を読んでいる方へ】本書のGemfileで固定した各gemのバージョンは公式リポジトリと一致している必要があります。現在の章に合わせてご参照ください。
リスト 3.2: サンプルアプリケーション用のGemfile Gemfile
source "https://rubygems.org"
git_source(:github) { |repo| "https://github.com/#{repo}.git" }

gem "rails",           "7.0.4"
gem "sassc-rails",     "2.1.2"
gem "sprockets-rails", "3.4.2"
gem "importmap-rails", "1.1.0"
gem "turbo-rails",     "1.1.1"
gem "stimulus-rails",  "1.0.4"
gem "jbuilder",        "2.11.5"
gem "puma",            "5.6.4"
gem "bootsnap",        "1.12.0", require: false

group :development, :test do
  gem "sqlite3", "1.4.2"
  gem "debug",   "1.5.0", platforms: %i[ mri mingw x64_mingw ]
end

group :development do
  gem "web-console", "4.2.0"
end

group :test do
  gem "capybara",                 "3.37.1"
  gem "selenium-webdriver",       "4.2.0"
  gem "webdrivers",               "5.0.0"
  gem "rails-controller-testing", "1.0.5"
  gem "minitest",                 "5.15.0"
  gem "minitest-reporters",       "1.5.0"
  gem "guard",                    "2.18.0"
  gem "guard-minitest",           "2.4.6"
end

group :production do
  gem "pg", "1.3.5"
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.3.14_ config set --local without 'production'
$ bundle _2.3.14_ install

上のオプションを指定することで、PostgreSQL用のpg gemをdevelopment環境にインストールせず、代わりにSQLiteがdevelopment環境とtest環境で使われるようになります。一般的には、development環境とproduction環境は極力同じに保ち、環境ごとの違いを作らないようにするのが理想的です。データベースもそれぞれの環境で同じにしておくのが本来望ましい方法ですが、幸いにもこのサンプルアプリケーションでは両者の違いは生じません。また、SQLiteの方がPostgreSQLよりもローカルでのインストールや設定がずっと楽なので、今回は異なるデータベースを使うことにします4

なお、ネイティブのWindowsシステム上でRailsを実行する場合は、 1.3.1で行ったように、リスト 3.2の最終行のコメントアウトを解除することをお忘れなく。

なお、Gemfileで指定されているのと異なるバージョンのgem(Rails自身のgemなど)をインストールしていた場合は、bundle updateを実行してgemを更新(update)し、gemのバージョンを合わせておくとよいでしょう。

$ bundle _2.3.14_ update

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

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

最初のアプリケーションのときと同様に、まずはアプリケーションのルートディレクトリにあるREADME.mdファイルを更新して、具体的な作業内容をわかりやすく記入しておくことをオススメします(リスト 3.3)。例えば次のように、READMEにはこのアプリケーションを使ってみる方法を記載してみましょう(6まではrails db:migrateが必要になることはありませんが、いずれ必要になるので今のうちに含めてしまいましょう)。

Note: 公式リポジトリのREADMEには、リスト 3.3には記載されていない追加情報も含めてありますので、ご活用ください。

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

これは、次の教材で作られたサンプルアプリケーションです。
[*Ruby on Rails チュートリアル*](https://railstutorial.jp/)
(第7版)
[Michael Hartl](https://www.michaelhartl.com/) 著

## ライセンス

[Ruby on Rails チュートリアル](https://railstutorial.jp/)内にある
ソースコードはMITライセンスとBeerwareライセンスのもとで公開されています。
詳細は [LICENSE.md](LICENSE.md) をご覧ください。

## 使い方

このアプリケーションを動かす場合は、まずはリポジトリを手元にクローンしてください。
その後、次のコマンドで必要になる RubyGems をインストールします。

```
$ gem install bundler -v 2.3.14
$ bundle _2.3.14_ config set --local without 'production'
$ bundle _2.3.14_ install
```

その後、データベースへのマイグレーションを実行します。

```
$ rails db:migrate
```

最後に、テストを実行してうまく動いているかどうか確認してください。

```
$ rails test
```

テストが無事にパスしたら、Railsサーバーを立ち上げる準備が整っているはずです。

```
$ rails server
```

詳しくは、[*Ruby on Rails チュートリアル*](https://railstutorial.jp/)
を参考にしてください。

READMEを書き終わったら、この変更をコミットします。

$ git commit -am "Improve the README"

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

1.4.3と同じ手順に沿ってGitHub上にリポジトリを作成し、リモートにもプッシュしておいてください。リポジトリは図 3.1のとおりprivateに設定します。

$ git remote add origin https://github.com/<あなたのGitHubアカウント名>/sample_app.git
$ git push -u origin main
7
図 3.1: メインのサンプルアプリのリポジトリをGitHub上に作成する

クラウドIDEをお使いの場合は、前の2つの章で行ったときと同様にdevelopment.rbファイルを編集し、アプリをローカルで起動できるようにする必要があります(リスト 3.4)。

リスト 3.4: Cloud9 への接続を許可する config/environments/development.rb
Rails.application.configure do
  .
  .
  .
  # Cloud9 への接続を許可する
  config.hosts.clear
end

後でproduction環境にプッシュするときに悩まずに済むよう、アプリをなるべく早い段階でデプロイしておくとよいでしょう。1のときと同様に、リスト 3.5リスト 3.6にある「hello, world」の手順に従って進めてみましょう。

(Railsのデフォルトのページは本番環境ではうまく表示されない仕様になっています。このため、次のような変更を加えないとデプロイが成功したかどうかが分かりづらい、というのが本当の理由です。)

リスト 3.5: helloアクションをApplicationコントローラーに追加する app/controllers/application_controller.rb
class ApplicationController < ActionController::Base

  def hello
    render html: "hello, world!"
  end
end
リスト 3.6: ルートルーティングを設定する config/routes.rb
Rails.application.routes.draw do
  root "application#hello"
end

この時点で、次のように変更をコミットしておきましょう。

$ git commit -am "Add hello"

ここまで終わったら、念のためリスト 1.9で紹介したbundle lockを行い、変更をコミットしてGitHubにプッシュします。

$ bundle _2.3.14_ lock --add-platform x86_64-linux
$ git commit -am "Include bundle lock"
$ git push

続いて、本番環境用のデータベースを用意します。Renderでは90日間(約3ヶ月間)無料で使えるPostgreSQLが提供されているので、サンプルアプリケーション用に作成します(図 3.2)。

7
図 3.2: RenderでPostgreSQLを作成する

NameとDatabase名を設定します(図 3.3)。Nameはダッシュボードに表示される名前で、後からも変更可能です。データベース自体の名前になるDatabase名は後から変更ができないので入力間違いに注意が必要ですが、もちろん削除して作り直すこともできます。なお、Database名はすでに作成された名前は使えないので、図 3.3と同じものは作成できない点にご注意ください。もし良い名前が思い付かない場合は、Nameをもとに自動でランダム作成されるので空欄でも構いません。同じくUser名も空欄の場合、Database名をもとに自動で生成されます。その他も空欄、またはデフォルトの設定のままFreeプランで作成します。

7
図 3.3: RenderでPostgreSQLを作成する

作成されたデータベースのConnectionsにある[Internal Database URL]は、後ほどWeb Serviceを作成する際に必要になります。

データベースが作成できたら、1.5の手順を参考にsample_app用のWeb Serviceを作成します。アプリケーション名を設定したら、環境変数にRAILS_MASTER_KEYを設定し、入力欄を追加してkeyにはDATABASE_URL、Valueには先ほど作成したデータベースの[Internal Database URL]を設定します(図 3.4)。

7
図 3.4: 環境変数を設定する

ところで、Renderのデフォルトの設定では、GitHubにプッシュすると自動でデプロイを行う「オートデプロイ」機能がオンになっています。詳しくは3.5で説明しますが、この時点でオートデプロイ機能をオフに設定しておきます(図 3.5)。

7
図 3.5: オートデプロイをオフにする

最後に“Create Web Service”をクリックすると、最初のデプロイが行われます。URL以外は、図 1.42のとおりに表示されるでしょう。

この後も、本チュートリアルを進めながらアプリケーションをこまめにプッシュ/デプロイすることをオススメします。こうすることでバックアップをリモートに保存できますし、本番環境でのエラーを早めに確認することもできます。なお、Renderにデプロイするときにエラーが発生した場合は、ダッシュボードの「Logs」から本番環境のログを取得してください。このログは、問題を特定するときに役立ちます。

演習

  1. Markdown記法のREADME(リスト 3.3)がHTMLとして正しくGitHub上で表示されているか、確認してみてください。
  2. 本番環境のルートURLにアクセスして、デプロイが成功したかどうか確かめてみてください。
7
図 3.6: GitHub上のサンプルアプリのREADME

3.2 静的ページ

3.1の準備がすべて完了したら、いよいよサンプルアプリケーションの開発に取りかかりましょう。

この節では、まずRailsのアクションビューを使って静的なHTMLのみのページを作成し、その後、静的なページを動的なページに作り変えていきます5

Railsのアクションは、コントローラ1.3.3で説明したMVCの「C」)の中に置きます。また、コントローラ内の各アクションは目的に沿って互いに関連した操作(作成や削除など)を行います。

コントローラについては2でも簡単に触れましたが、6で説明するRESTアーキテクチャを読むと理解が深まります。一言でまとめると、コントローラとは(基本的に動的な)Webページの集合を束ねるコンテナのことです。

現在どのディレクトリで作業しているかがわからなくなった場合は、1.3図 1.13)を再度参照して、Railsのディレクトリ構造を確認してください。この節では、主にapp/controllersディレクトリやapp/viewsディレクトリ内で作業を進めます

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

$ git switch -c static-pages

バージョン管理を行う場合は、適度にこまめなコミットを行うのがよい習慣です。逆に、たくさんの変更をためて1個の巨大なコミットにまとめるのは避ける方がよいでしょう。他の人がコミットを読むのがつらくなってしまいます。本チュートリアルでは、コミットメッセージが細かくなりすぎないように、各章の最後でコミットするようにしていますが、業務でバージョン管理を行うのであれば、コミット頻度の目安として各節ごと(あるいはその下の小節ごと)にコミットするぐらいでもよいでしょう。

3.2.1 静的なページの生成

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

リスト 3.7: StaticPagesコントローラを生成する
$ rails generate controller StaticPages home help
      create  app/controllers/static_pages_controller.rb
       route  get "static_pages/home"
get "static_pages/help"
      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

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

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

次に進む前に、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では、コントローラ名をキャメルケース(CamelCase: 単語の頭文字を大文字にしてつなぎ合わせた名前)で渡していることに注目してください。こうすると、StaticPagesコントローラ名をスネークケース(snake_case: 単語間にアンダースコアを加えてつなぎ合わせた名前)にしたファイル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.7のように、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で説明しますが、簡単に紹介すると、まずdb:migrateでデータベースのマイグレーションを変更します。

  $ rails db:migrate

元に戻したいときは、db:rollbackで1つ前の状態に戻します。

  $ rails db:rollback

最初の状態に戻したいときは、VERSION=0オプションを使います。

  $ rails db:migrate VERSION=0

ちなみに、上のコマンドのVERSION=0は、最初のシリアル番号、つまり最初に行ったマイグレーションに戻すという意味です。以前のRailsでは、ここに0以外のシリアル番号を指定すると、そのシリアル番号に該当する作成日時のマイグレーションに戻すことが可能でした。現在のマイグレーションは、VERSION=20220407092210のようにマイグレーションの作成日時のみ指定可能ですが、例外的にVERSION=0というシリアル番号だけは現在もサポートされています。

開発中に行き詰まってしまった場合でも、これらの機能を使えば元の状態を復元できます。

リスト 3.7のようにStaticPagesコントローラを生成すると、(config/routes.rb)ファイルが自動的に更新されます。これは1.3.4でhelloアプリのルート(root)ルーティングを編集したときにリスト 1.14で確認し、本章でもリスト 3.6で確認しました。このルーティングファイルはルーターの実装を受け持ち(図 2.12)、URLとWebページの対応関係を定義します。このルーティングファイルはRailsのconfigディレクトリの下に置かれます。このディレクトリには、Railsの設定ファイルがまとめて置かれます(図 3.7)。

7
図 3.7: サンプルアプリケーションのconfigディレクトリの内容

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

リスト 3.8: StaticPagesコントローラ内の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.8)。

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

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

他にも、PATCHDELETEという2つの操作があり、それぞれサーバー上の何かを更新したり削除したりするときに使われます。これら2つの操作は、GETPOSTほどは使われていません。これは、ブラウザがPATCHとDELETEをネイティブでは送信しないからです。しかし、Ruby on Railsなどの多くのWebフレームワークは、ブラウザがこれらの操作のリクエストを送信しているかのように見せかける技術(偽装)を駆使して、PATCHとDELETEという操作を実現しています。

その結果、Railsはこの4つのHTTPリクエスト(GETPOSTPATCHDELETE)を全てサポートできるようになっています。

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

リスト 3.9: リスト 3.7で生成されるStaticPagesコントローラ app/controllers/static_pages_controller.rb
class StaticPagesController < ApplicationController
  def home
  end

  def help
  end
end

リスト 3.9classというキーワードから、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.7をもう一度注意深く読んでみると、アクションとビューの関係を推測できるでしょう。homeアクションはhome.html.erbというビューに対応しています。.erbの詳細については3.4で説明しますが、ファイル名に.htmlが含まれていることからわかるように、基本的にはHTMLと同じような構造になっています(リスト 3.10)。

リスト 3.10: 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.11)。

リスト 3.11: 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タグの中に書かれています。

演習

  1. Fooというコントローラを生成し、その中にbarbazアクションを追加してみてください。
  2. コラム 3.1で紹介したテクニックを駆使して、Fooコントローラとそれに関連するアクションを削除してみてください。

3.2.2 静的なページの調整

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

リスト 3.12: HomeページのHTMLを修正する 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>
リスト 3.13: 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="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.9図 3.10に示します。

7
図 3.9: 修正されたHomeページ
7
図 3.10: 修正されたHelpページ

3.3 テストから始める

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

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

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

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

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

上の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)。

リスト 3.14: StaticPagesコントローラのデフォルトのテスト green 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コマンドを使います。

リスト 3.15: green
$ rails db:migrate     # システムによっては必要
$ rails test
2 runs, 2 assertions, 0 failures, 0 errors, 0 skips

テストスイートは期待どおり成功( green )します(システムによっては、成功時に色を表示させるためには、3.6.1で説明するminitest reportersを追加する必要がありますが、単に見やすくするためのものなので必須ではありません)。この箇所も含めて、本チュートリアルでは重要な部分に注目しやすくするため、テストの出力結果の一部を省略しています。

ところで、一部のシステムでは以下のようなファイルがdbディレクトリの下に生成されていることがあります。

/db/test.sqlite3-0

この生成ファイルがGitリポジトリに登録されると邪魔なので、初回のリポジトリ・セットアップ(1.4.1.2)で紹介した.gitignoreファイルには、デフォルトでリスト 3.16の記述があります。見つからない場合は登録を防ぐために追加することをオススメします。

リスト 3.16: 生成されたデータベースファイルはGitに登録しない .gitignore
.
.
# Ignore the default SQLite database.
/db/*.sqlite3
/db/*.sqlite3-*
.
.

3.3.2 Red

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

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

リスト 3.17: Aboutページのテスト red 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.17のハイライト行を見ると、他のHomeページ用テストやHelpページ用テストとほとんど同じであることがわかります。違いは「home」や「help」の部分が「about」に変わっている点だけです。

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

リスト 3.18: red
$ rails test
3 runs, 2 assertions, 0 failures, 1 errors, 0 skips

3.3.3 Green

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

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

リスト 3.19: red
$ rails test
NameError: undefined local variable or method `static_pages_about_url'

このエラーメッセージによれば、「AboutページへのURLが見つからない」とあります。このメッセージをヒントに、ルーティングファイルを修正してみましょう。リスト 3.8のときと同じ要領で、変更を行った結果をリスト 3.20に示します。

リスト 3.20: 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.20のハイライト行では、/static_pages/aboutというURLに対してGETリクエストが来たら、StaticPagesコントローラのaboutアクションに渡すようRailsに指示しています。この結果、自動的に次のようなヘルパーが使えるようになります。

static_pages_about_url

修正が終わったらテストスイートをもう一度実行してみましょう。まだ red のままです。しかし今度はメッセージが少し変わりました。

リスト 3.21: red
$ rails test
AbstractController::ActionNotFound:
The action 'about' could not be found for StaticPagesController

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

リスト 3.22: 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::MissingExactTemplate: StaticPagesController#about
is missing a template for request formats: text/html

今度はテンプレートがないようです。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.23のとおりに内容を書き換えます。

リスト 3.23: Aboutページのコード green 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 app for the tutorial.
</p>

今度のrails testの結果は green になるはずです。

リスト 3.24: green
$ rails test
3 runs, 3 assertions, 0 failures, 0 errors, 0 skips

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

7
図 3.11: 新しいAboutページ(/static_pages/about

3.3.4 Refactor

テストが green になったので、安心してコードをリファクタリングできるようになりました。

アプリケーションの開発を繰り返すうちに、コードのどこからともなく「腐敗臭」が漂い始めます。コードや記法の統一が崩れて読みづらくなる、クラスやメソッドが何百行にも膨れ上がって読む気を削がれる、なぜこのコードがここにあるのか、もはや誰もその理由を思い出せなくなる、同じコードがあちこちにコピペされて少しずつ書き換えられ手に負えなくなる、などです。

コンピュータは、どんなに汚いコードであっても書かれた通りにひたすら実行しますが、人間はそういうわけにはいきません。こまめにリファクタリングを繰り返してコードを常にすみずみまで美しくコンパクトに保ち、他の開発者や未来の自分の開発意欲を削ぐことのないようにしなければなりません。このサンプルアプリは生まれたてなので、今のところリファクタリングの必要な箇所はほとんど見当たりません。しかし「1匹いれば30匹いると思え」ということわざのように、コードの腐敗臭はどんな小さな隙間からも忍び寄ってきます。こまめなリファクタリングの習慣をできるだけ早いうちに身につけるためにも、あえて3.4.3から始めることにします。

3.4 少しだけ動的なページ

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

ここでの目標は、Homeページ、Helpページ、Aboutページをそれぞれ編集し、最終的にページごとに異なるタイトルを表示することです。ここではビューの<title>タグの内容を変更します。多くのブラウザでは、titleタグの内容をブラウザウィンドウの上部にウィンドウタイトルとして表示します。titleタグは、いわゆるSEO(Search Engine Optimization: 検索エンジン最適化)においても重要な役割を果たします。

今度は「 redgreen ・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.2: サンプルアプリケーションの(ほぼ)静的なページ。

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

ページタイトルを追加する前に、HTMLについて今一度おさらいしておきましょう。典型的なWebページは、リスト 3.25のようなHTMLの構造を持っています(HTMLの詳細は HTML編を参照)。

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

リスト 3.25の構造には次の3つが含まれています。 1)document type(doctype)は使用するHTMLのバージョン(ここではHTML5)をブラウザに対して宣言します11。 2)headセクション。ここではtitleタグに囲まれた「Greeting」(=あいさつ)という文字があります。 3)bodyセクション。ここには「Hello, world!」という文字列があります。「Hello, world!」は、p(paragraph)タグの中にあります12

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

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

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

リスト 3.26: StaticPagesコントローラのタイトルをテストする red 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.26どおりにテストを作成すると、テストスイートは red になります。

リスト 3.27: red
$ rails test
3 runs, 6 assertions, 3 failures, 0 errors, 0 skips

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

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

リスト 3.28: 完全な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="https://railstutorial.jp/">Ruby on Rails Tutorial</a>
      sample application.
    </p>
  </body>
</html>

このページの表示を図 3.12に示します。なお、本チュートリアルのスクリーンショットで使われているSafariブラウザは、タブが2つ以上になった場合に図 3.12のようにページタイトルのみを表示します。

7
図 3.12: タイトルが付いたHomeページ

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

リスト 3.29: 完全な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="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>
リスト 3.30: 完全な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>
      <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 app for the tutorial.
    </p>
  </body>
</html>

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

リスト 3.31: green
$ rails test
3 runs, 6 assertions, 0 failures, 0 errors, 0 skips

演習

本演習からサンプルアプリケーションを修正する課題を出していきますが、基本的には演習で修正した結果が今後の開発に影響を与えないよう配慮してあります。なぜこんなことを今説明しているのかというと、今後の演習を進めていくうちに、本書のソースコードと読者のソースコードが少しずつ変わっていく可能性があるからです。しかし、そういった差異から生じる問題を解決していくことは、「熟練」に至るまでの学びには欠かせません(コラム 1.2)。ぜひ次の演習にも果敢に取り組んでみてください。

  1. StaticPagesコントローラのテスト(リスト 3.26)には、いくつか繰り返しがあったことにお気づきでしょうか? 特に「Ruby on Rails Tutorial Sample App」という基本タイトルは、各テストで毎回同じ内容を書いてしまっています。そこで、setupという特別なメソッド(各テストが実行される直前で実行されるメソッド)を使って、この問題を解決したいと思います。まずは、リスト 3.32のテストが green になることを確認してみてください(リスト 3.32では、2.2.2で少し触れたインスタンス変数文字列の式展開というテクニックを使っています。それぞれ4.4.54.2.1で詳しく解説するので、今はわからなくても問題ありません)。
リスト 3.32: 基本タイトルを使ったStaticPagesコントローラのテスト green 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 レイアウトとERB(Refactor)

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

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

同じコードを何度も繰り返し書くと、Rubyの「DRY」(Don’t Repeat Yourself: 繰り返すべからず)という原則に反します。この節では、繰り返し(重複)を追放してコードをDRYにしましょう。最後に3.4.2のテストを再度実行して、タイトルが正しいことを確認します。

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

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

リスト 3.33: タイトルに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="https://railstutorial.jp/">Ruby on Rails Tutorial</a>
      sample application.
    </p>
  </body>
</html>

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

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

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

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

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

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

リスト 3.34: green
$ rails test
3 runs, 6 assertions, 0 failures, 0 errors, 0 skips

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

リスト 3.35: タイトルに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="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>
リスト 3.36: タイトルに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>
      <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, "Page 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.37のようになります。

リスト 3.37: サンプルアプリケーションのレイアウト green app/views/layouts/application.html.erb
<!DOCTYPE html>
<html>
  <head>
    <title><%= yield(:title) %> | Ruby on Rails Tutorial Sample App</title>
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <meta charset="utf-8">
    <%= csrf_meta_tags %>
    <%= csp_meta_tag %>

    <%= stylesheet_link_tag "application", "data-turbo-track": "reload" %>
  </head>

  <body>
    <%= yield %>
  </body>
</html>

上のコード内にある、次の特殊なコードにご注目ください。

<%= yield %>

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

また、リスト 3.37には「文字セット(character set)」を表すmetaタグも含まれています。ここでは、UTF-8という文字エンコーディングが指定されています。UTF-8エンコーディングは、「™」や「✓」などのUnicode文字をサポートします。

最後に、Railsのデフォルトのレイアウトでは、次の行が追加されていることにもご注目ください。

<%= csrf_meta_tags %>
<%= csp_meta_tag %>
<%= stylesheet_link_tag ... %>

上のERBは、スタイルシートとJavaScriptをページ内にインクルードする(=含める)ためのものです。スタイルシートとJavaScriptは、アセットパイプライン(5.2.1)の一部であり、クロスサイトスクリプティング攻撃を緩和するコンテンツセキュリティポリシー(Content Security Policy: CSP)を実装するcsp_meta_tagと、クロスサイトリクエストフォージェリー(Cross-Site Request Forgery: CSRF)攻撃を緩和するcsrf_meta_tagsも含みます。Railsのような長年実績を積んでいるフレームワークを使うと、開発者がこのようなセキュリティ対策を気にせずに済むのが大きなメリットです。

テストが無事パスしても、まだやることは残っています。リスト 3.33リスト 3.35リスト 3.36のビューには、レイアウトと重複する不要なHTML構造がまだ残っていて、このままではHTMLマークアップが壊れてしまうので、この不要なHTML構造を削除して、内部のコンテンツだけ残します。この改修が終わると、リスト 3.38リスト 3.39リスト 3.40のように実に簡潔で美しいコードになります。

リスト 3.38: 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="https://railstutorial.jp/">Ruby on Rails Tutorial</a>
  sample application.
</p>
リスト 3.39: 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="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.40: HTML構造を削除したAboutページ green 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です。

リスト 3.41: green
$ rails test
3 runs, 6 assertions, 0 failures, 0 errors, 0 skips

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

演習

  1. サンプルアプリケーションにContact(問い合わせ先)ページを作成してください17。(ヒント: まずはリスト 3.17を参考にして、/static_pages/contactというURLのページに「Contact | Ruby on Rails Tutorial Sample App」というタイトルが存在するかどうかを確認するテストを最初に作成しましょう。次に、3.3.3でAboutページを作ったときのと同じように、Contactページにもリスト 3.42のコンテンツを表示してみましょう。)
リスト 3.42: 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="https://railstutorial.jp/contact">contact page</a>.
</p>

3.4.4 ルーティングの設定

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

root "application#hello"

次のコードに置き換えています。

root "static_pages#home"

上のルールに置き換えるとコントローラ/アクションへの対応付けが変わり、ルート「/」へのGETリクエストがStaticPagesコントローラのhomeアクションにルーティングされます。無事に変更できれば、図 3.13のようになるはずです。

リスト 3.43: HomeページをルートURLに設定する 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
7
図 3.13: ルートURLにアクセスするとHomeページが表示される

演習

  1. リスト 3.43rootルーティングを追加したことで、root_urlというRailsヘルパーが使えるようになりました(以前、static_pages_home_urlが使えるようになったときと同じです)。リスト 3.44(コードを書き込む)と記された部分を置き換えて、rootルーティングのテストを書いてみてください。
  2. 実はリスト 3.43のコードを書いていたので、先ほどの課題のテストは既に green になっているはずです。このような場合、テストを変更する前から成功していたのか、変更した後に成功するようになったのかを判断するのが難しくなります。リスト 3.43のコードがテスト結果に影響を与えていることを確認するため、リスト 3.45のようにrootルーティングを試しにコメントアウトし、 red になるかどうか確かめてみましょう(なおRubyのコメント機能については4.2で説明します)。最後に、コメントアウトした箇所を元に戻し(すなわちリスト 3.43に戻し)、テストが green になることを確認してみましょう。
リスト 3.44: rootルーティングに対するテスト 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
リスト 3.45: rootルーティングをコメントアウトしてテストを失敗させる red 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のコントローラ、アクション、ビューの開発をひととおり行ったことで、これから動的なコンテンツをどしどしサイトに追加するための準備がすっかり整ったのです。残る課題は、皆さんがこのチュートリアルをいかに最後までやりぬくか、それだけであると言ってよいでしょう。

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

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

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

$ git switch main
$ git merge static-pages

このようにきりのいいところまで達したら、コードをリモートリポジトリにアップロードしておくとよいでしょう(1.4.3の手順に従っていれば、リモートリポジトリにはGitHubが使われます)。git pushする前にテストを走らせていますが、こういった習慣を身につけておくと開発に役立ちます。

$ rails test
$ git push

最後にRender上でデプロイを行います。3.1では、Renderのオートデプロイをオフにしました(図 3.5)。オートデプロイは、実際のサービス開発などでは便利に使える機能ですが、本チュートリアルでは、どのような変更を本番環境に適用したのかをわかりやすくするため、あえて手動でデプロイを行います。

図 3.14に示す、ダッシュボード右上の“Manual Deploy”から、Deploy latest commitを選択すると、mainブランチ上の最後のコミットをもとにデプロイが行われます。

7
図 3.14: 手動デプロイを行う

デプロイが成功することをダッシュボードのログで確認し、本番環境でも正しく表示されているか確認してみましょう。

3.5.1 本章のまとめ

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

3.6 高度なセットアップ

この追加の節は、テスト用設定について解説します。大きく分けると、成功/失敗の表示設定をする「minitest reporters(3.6.1)」と、ファイルの変更を検出して必要なテストだけを自動実行してくれる「Guard(3.6.2)」の2つです。この節で参考までに示したコードはそれなりに高度なので、今すぐ理解できるようになる必要はありません。

なお、このセクションで紹介するセットアップはmainブランチで行う必要があるので、今のうちにブランチをmainに切り替えておきましょう。

$ git switch main

3.6.1 minitest reporters

redgreen の色は、クラウドIDEなど多くのシステムでは何もしなくても適切に表示されますが、minitest reportersを使うとテスト結果の出力を見やすくフォーマットしてくれます。そのため筆者は、リスト 3.46 のコードをテストヘルパーファイルに追加することで、リスト 3.2のGemfileにあるminitest-reporters gemを利用することをオススメします。

リスト 3.46: テストで redgreen を表示できるようにする 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.15)。

7
図 3.15: クラウドIDE上での表示が red から green に変わった

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

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

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

$ bundle _2.3.14_ 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.47)。柔軟性を最大にするため、チュートリアルの参照用アプリケーションに記載されているのと同じバージョンのGuardfileをお使いになることをオススメします。本チュートリアルをオンラインでお読みの方は、リスト 3.47で正確な書き方を参照できます。

リスト 3.47: カスタマイズしたGuardfile
  require "active_support/inflector"
  # Guardのマッチング規則を定義
  guard :minitest, 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",
       "test/integration/microposts_interface_test.rb"]
    end
    watch(%r{^test/fixtures/(.*?)\.yml$}) do |matches|
      "test/models/#{matches[1].singularize}_test.rb"
    end
    watch(%r{^app/mailers/(.*?)\.rb$}) do |matches|
      "test/mailers/#{matches[1]}_test.rb"
    end
    watch(%r{^app/views/(.*)_mailer/.*$}) do |matches|
      "test/mailers/#{matches[1]}_mailer_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
    watch('app/controllers/relationships_controller.rb') do
      ['test/controllers/relationships_controller_test.rb',
       'test/integration/following_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

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

$ bundle _2.3.14_ exec guard

リスト 3.47のルールは本チュートリアルに最適化したものなので、例えばコントローラのファイルを変更すると、Guardは即座にそれを検出して、そのコントローラの統合テストを自動実行します。テストを変更ファイルだけではなく、フルで実行したい場合は、guard>プロンプトでReturnキーを押します。

7
図 3.16: クラウドIDEでGuardを使う

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

次の章に進む前に、ここまでの変更を忘れずにコミットしておきましょう。

$ git add -A
$ git commit -m "Complete advanced testing setup"
1. クラウドIDEをお使いの場合は、[Go]メニューの「Go to Anything」コマンド(ファイル名の一部を入力するだけでその場所にジャンプする)が重宝します。「hello」アプリ「toy」アプリ「sample」アプリには同じファイル名が多数あるため、これらのアプリを同じプロジェクトに置くとファイルを見つけにくくなることがあります。例えば「Gemfile」というファイル名を検索すると、GemfileGemfile.lockを含め、候補が6つも表示されてしまいます。そこで、この先に進む前に先の2つのアプリを思い切って削除しておくとよいでしょう。アプリを削除するには、environmentディレクトリに移動してrm -rf hello_app/ toy_app/コマンドを実行します(表 1.1)。これらのアプリを既にGitHubのリポジトリにプッシュしてあるなら、必要に応じてそれを利用していつでもアプリを復元できます。
2. 何か問題が生じた場合は、 1.2.1で指示した通りにRuby 3.1.2を使っているかどうかを確認してください。
3. この--local without 'production'オプションは、一度実行すれば「記憶される」ことを知っておくとよいでしょう。つまり、次回以降bundle installを実行すると、このオプションが暗黙的に適用されるようになります。
4. 最終的には皆さんがPostgreSQLをdevelopment環境にインストールして設定できるようになるのが理想ですが、今は時期尚早であると考えます。実際に必要が生じたときは「install configure postgresql <自分のシステム>」や「rails postgresql setup」でググって各自挑戦してみてください(クラウドIDEの場合は<自分のシステム>にLinuxと指定します)。
5. ここで静的なページを作るために採用した方法は、おそらく最もシンプルな方法です。ただし他にも方法はあります。最適な方法は状況によって異なり、例えば極めて多数の静的なページを1つのStaticPagesコントローラだけでまかなおうとすると重荷になる可能性があります。今回はいくつかの静的なページを作るだけなので、重荷にはなりません。もし多数の静的なページが必要になる場合は、thoughtbothigh_voltage gem を調べてみてください。
6. 実際、多くのRailsエンジニアはエイリアス(alias)と呼ばれるショートカットを作っています。例えばrと実行するとrailsコマンドが実行される、といった具合です。このエイリアスを設定していれば、$ r sとコマンドを打つだけでRailsサーバーを立ち上げられるようになります。詳しくは テキストエディタ編「ファイルを保存してVimを終了する」を読んでみてください。
7. 詳しくはRails生みの親であるDavid Heinemeier Hansson(通称DHH)の記事『TDD is dead. Long live testing.』をご覧ください。
8. システムによっては、rails testでテストが失敗すると赤色で表示するのに、テストが成功しても緑色で表示しないことがあります。色もちゃんと表示したい場合は3.6.1をご覧ください。
9. 本チュートリアル執筆時点では、この失敗するテストの実行時間や出力結果が極端に長くなるという問題が生じています。(この後の章でもしばしば発生しています。)以前はこのようなことにはならなかったので、Railsで近いうちに修正されることを願っていますが、さしあたってrails test | grep -v UnexpectedErrorというふうに コマンドライン編で学んだ「パイプ」で出力をgrepコマンドにつなぐことで回避できます。ここで使われている-vフラグは、UnexpectedErrorという文字列にマッチしないものを出力するという意味です。-vフラグをうまく活用すると、「マッチするものを出力」を手軽に「マッチしないものを出力」に反転できます。
10. システムによっては、ソースコードのエラーパスを追跡する「スタックトレース」または「バックトレース」と呼ばれるメッセージが大量に表示され、上にスクロールしてもメッセージまで遡れないかもしれません。この場合は、/static_pages/aboutにアクセスしてみる、その際にrails serverのログを見てみるなど、ヒントとなるメッセージを探してみましょう。
11. HTMLの仕様は時とともに変わる可能性があると思っておく方がよいでしょう。今後もブラウザでなるべく正しくページを表示できるように、doctypeを明示的に宣言しています。追加属性がまったくないシンプルな「<!DOCTYPE html>」は、最新標準であるHTML5の特徴です。
12. HTMLではスペースやタブが無視されるので、インデントはあってもなくても大丈夫です。しかしインデントがある方が、HTMLのデータ構造を理解しやすくなります。
13. minitestで利用できるアサーションのリストについては、Railsガイドの『Railsテスティングガイド - 利用可能なアサーション』を読んでみてください。
14. 2番目に人気のテンプレートとしてHamlがあり(注意: "HAML"ではありません)、筆者は個人的にHamlの方が気に入っています。残念ながら十分に普及していないため、初級者向けチュートリアルの採用は見送りました。
15. Railsでの開発経験者であれば、この時点でcontent_forメソッドの利用を検討すると思いますが、残念ながらアセットパイプラインと併用すると正常に動作しないことがあります。provideメソッドはcontent_forの代替です。
16. Rubyを勉強したことのある方なら、Railsはブロックの内容をyieldしていると推測することでしょう。そして、その推測はおそらく正しいでしょう。しかし、Rails開発のためにこれらの詳細を知る必要はありません。
17. この演習の解答は5.3.1にあります。
前の章
第3章 ほぼ静的なページの作成 Rails 7 (第7版)
次の章