Ruby on Rails チュートリアル

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

第2版 目次

第7章ユーザー登録

Userモデルができあがったので、いよいよWebサイトになくてはならないユーザー登録機能を追加しましょう。7.2ではHTMLフォームを使用して登録情報をWebアプリケーションに送信します。続いて7.4ではユーザーを新規作成して情報をデータベースに保存します。ユーザー登録手続きの最後には、作成されたユーザーの新しいプロファイルを表示できるようにするために、ユーザーを表示するためのページを作成し、ユーザー用のRESTアーキテクチャを実装する第一歩を踏み出します (2.2.2)。これまでと同様、開発と同時にテストも作成します。RSpecとCapybaraの適用範囲を拡大し、簡潔かつ表現力豊かな結合テストを作成します。

ユーザープロファイルページを作成するには、その前にデータベースにユーザーが登録されている必要があります。これはいわゆる「卵が先か鶏が先か」問題です。このWebサイトでは、登録ページがない状態でどうやってユーザーを登録しておけばよいでしょうか。幸い、この問題は既に解決されています。6.3.5でRailsコンソールを使用してユーザーレコードを登録してありました。登録していない場合は、上記を参照して登録しておいてください。

バージョン管理を使用している場合は、いつもと同じようにトピックブランチを作成します。

$ git checkout master
$ git checkout -b sign-up

7.1ユーザーを表示する

この節では、まずユーザーの名前とプロファイル写真を表示するためのページを作成します。モックアップを図7.1に示しました1。 ユーザープロファイルページの最終的な目標は、図7.22のようにユーザーのプロファイル写真と基本ユーザーデータ、そしてマイクロポストの一覧を表示することです。(図7.2では、有名なlorem ipsumダミーテキストを使用しています。このテキストの成り立ちには面白いエピソードがあるので機会がありましたらどうぞ。) このページを作成したら、第11章のサンプル・アプリケーションで使用する予定です。

profile_mockup_profile_name_bootstrap
図7.1この節で作成するユーザープロファイルのモックアップ。(拡大)
profile_mockup_bootstrap
図7.2理想とする最終的なプロファイルページのモックアップ。(拡大)

7.1.1デバッグとRails環境

この節で作成するプロファイルは、このアプリケーションにおける初めての真に動的なページになります。ビューそのものは1ページのコードですが、サイトのデータベースから取り出した情報を使用して各プロファイルの表示をカスタマイズします。サンプルアプリケーションに動的なページを追加する準備として、ここでWebサイトのレイアウトにデバッグ情報を追加しましょう (リスト7.1)。これにより、ビルトインのdebugメソッドとparams変数を使用して、各プロファイルページにデバッグ用の情報が表示されるようになります (詳細については7.1.2で解説します)。

リスト7.1 サイトのレイアウトにデバッグ情報を追加する。
app/views/layouts/application.html.erb
<!DOCTYPE html>
<html>
  .
  .
  .
  <body>
    <%= render 'layouts/header' %>
    <div class="container">
      <%= yield %>
      <%= render 'layouts/footer' %>
      <%= debug(params) if Rails.env.development? %>
    </div>
  </body>
</html>

デバッグ出力をきれいに整形するために、第5章で作成したカスタムスタイルシートをリスト7.2のように追加します。

リスト7.2 デバッグ表示を整形するための追加と、Sassのミックスイン。
app/assets/stylesheets/custom.css.scss
@import "bootstrap";

/* mixins, variables, etc. */

$grayMediumLight: #eaeaea;

@mixin box_sizing {
  -moz-box-sizing: border-box;
  -webkit-box-sizing: border-box;
  box-sizing: border-box;
}
.
.
.

/* miscellaneous */

.debug_dump {
  clear: both;
  float: left;
  width: 100%;
  margin-top: 45px;
  @include box_sizing;
}

ここでSassのミックスイン機能 (ここではbox_sizing) を使用しています。ミックスイン機能を使用することで、CSSルールのグループをパッケージ化して複数の要素に適用することができます。たとえば以下のような変換を行います。

.debug_dump {
  .
  .
  .
  @include box_sizing;
}

上が以下のように変換されます。

.debug_dump {
  .
  .
  .
  -moz-box-sizing: border-box;
  -webkit-box-sizing: border-box;
  box-sizing: border-box;
}

ミックスインは7.2.2でも使用します。今回の場合、デバッグ出力は図7.3のようになります。

home_page_with_debug_4_0
図7.3サンプルアプリケーションのHomeページ (/) にデバッグ情報を表示する。(拡大)

図7.3のデバッグ出力には、描画されるページの状態を把握するのに役立つ情報が含まれます。

---
controller: static_pages
action: home

これはparamsの内容で、YAML3というものです。YAMLは基本的にハッシュであり、コントローラとページのアクションを一意に指定します。7.1.2には別の例もあります。

本番環境にデプロイしたアプリケーションではデバッグ情報を表示したくないので、リスト7.1には以下を記述してあります。

if Rails.env.development?

こうしておくと、デバッグ情報はRailsの3つのデフォルト環境のうち、開発環境でしか表示されなくなります (コラム 7.1)4。特に、Rails.env.development?trueになるのは開発環境に限られるため、以下の埋め込みRubyは

<%= debug(params) if Rails.env.development? %>

本番アプリケーションやテストで挿入されることはありません。(テスト環境でデバッグ情報が表示されても直接問題になることはありませんが、よいことではありません。デバッグ情報は開発環境以外では使用すべきではありません。)

7.1.2ユーザーリソース

第6章の最後で、データベースに新しいユーザーを作成しました。6.3.5でこのユーザーのidは1になっており、今の私たちの目標はこのユーザーの情報を表示するページを作成することです。ここでは、Railsアプリケーションで好まれているRESTアーキテクチャ (コラム 2.2) の習慣に従うことにしましょう。つまり、データを作成、表示、更新、削除可能なリソースとして扱うということです。HTTP標準には、これらに対応する4つの基本操作 (POSTGETPATCHDELETE)が定義されています (コラム  3.3)。

RESTの原則に従う場合、リソースへの参照はリソース名とユニークIDを使用するのが普通です。ユーザーをリソースとみなす場合、id=1のユーザーを参照するということは、/users/1というURLに対してGETリクエストを発行するということを意味します。ここでshowというアクションの種類は、暗黙のリクエストになります。RailsのREST機能が有効になっていると、GETリクエストは自動的にshowアクションとして扱われます。

2.2.1で説明したとおり、id=1のユーザーにアクセスするためのページのURIは/users/1となります。ただし、現時点でこのURIを使用してもエラーになります (図 7.4)。

profile_routing_error
図7.4/users/1にアクセスした時のエラーページ。(拡大)

RESTスタイルのURLを有効にするには、routesファイル (config/routes.rb)に以下の1行を追加します。

resources :users

変更の結果をリスト7.3に示します

リスト7.3 Usersリソースをroutesファイルに追加する。
config/routes.rb
SampleApp::Application.routes.draw do
  resources :users
  root  'static_pages#home'
  match '/signup',  to: 'users#new',            via: 'get'
  .
  .
  .
end

ところで、リスト7.3には以下の行がありません。

get "users/new"

この行はリスト5.33にはありました。実は、resources :usersという行は、動作する /users/1 URLを追加するためだけのものではありません。サンプルアプリケーションにこの行を追加すると、ユーザーのURLを生成するための多数の名前付きルート (5.3.3) に従って、RESTfulなUsersリソースで必要となるすべてのアクションが利用できるようになります5。この行に対応するURL、アクション、名前付きルートは表7.1のようになります (表2.2との違いを比較してみてください)。 次の3つの章に渡って、表7.1の他の項目も利用して、Usersリソースを完全にRESTfulなリソースにするために必要なアクションをすべて作成する予定です。

HTTPリクエストURLアクション名前付きルート用途
GET/usersindexusers_pathすべてのユーザーを表示するページ
GET/users/1showuser_path(user)特定のユーザーを表示するページ
GET/users/newnewnew_user_pathユーザーを新規作成するページ (ユーザー登録)
POST/userscreateusers_pathユーザーを作成するアクション
GET/users/1/editeditedit_user_path(user)id=1のユーザーを編集するページ
PATCH/users/1updateuser_path(user)ユーザーを更新するアクション
DELETE/users/1destroyuser_path(user)ユーザーを削除するアクション
表7.1リスト7.3のUsersリソースが提供するRESTfulなルート。

リスト7.3のコードを使用することで、ルーティングが有効になります。ただし、ルーティング先のページはまだありません (図7.5)。この問題を解決するために、7.1.4で最小限のプロファイルページを作成する予定です。

user_show_unknown_action_4
図7.5URI /users/1のルーティングは有効だがページがない状態。(拡大)

ユーザーを表示するために、標準的なRailsの場所を使用することにします。app/views/users/show.html.erbです。リスト5.29でジェネレータを使用して作成したnew.html.erbビューと異なり、このshow.html.erbファイルは自動的には作成されないので、手動で作成します。このファイルを作成後、リスト7.4の内容を貼り付けてください。

リスト7.4 ユーザー情報を表示するための仮のビュー。
app/views/users/show.html.erb
<%= @user.name %>, <%= @user.email %>

このビューでは埋め込みRubyを使用してユーザー名とメールアドレスを表示しています。インスタンス変数@userがあることを前提としています。もちろん、ユーザー表示ページの最終的な状態はこれとは大きく異なりますし、このメールアドレスがこのまま一般に公開されるようなこともありません。

ユーザー表示ビューが正常に動作するためには、Usersコントローラ内のshowアクションに対応する@user変数を定義する必要があります。 ご想像のとおり、ここではUserモデルのfindメソッド (6.1.4) を使用してデータベースからユーザーを取り出します。リスト7.5のように書き換えてください。

リスト7.5 Usersコントローラのshowアクション。
app/controllers/users_controller.rb
class UsersController < ApplicationController

  def show
    @user = User.find(params[:id])
  end

  def new
  end
end

ユーザーのid読み出しにはparamsを使用しました。Usersコントローラにリクエストが正常に送信されると、params[:id]の部分はユーザーidの1に置き換わります。従って、この箇所は以下のfindメソッドと同等です。

User.find(1)

上は6.1.4で使用したものです (技術的な補足: params[:id]は文字列型の "1" ですが、findメソッドでは自動的に整数型に変換されます)。

ユーザーのビューとアクションが定義されたので、URI /users/1は完全に動作するようになりました (図7.6)。図7.6のデバッグ情報でparams[:id]の値を確認できることにも注目してください。

---
action: show
controller: users
id: '1'

以下のコードを使用して

User.find(params[:id])

id=1のユーザーを検索できたのは以上の仕組みによるものです (リスト7.5)。

user_show_bootstrap
図7.6Usersリソース追加後の/users/1のユーザー表示ページ。 (拡大)

7.1.3ファクトリーを使用してユーザー表示ページをテストする

最小限のプロファイルが動作するようになりましたので、図7.1のモックアップバージョンの作成を始めましょう。静的なページの作成 (第3章) やUserモデル (第6章) のときと同様に、テスト駆動開発を行います。

5.4.2では、Usersリソースに関連付けられたページに対して行った結合テストを思い出してください。ユーザー登録ページでは、リスト5.32のように最初にsignup_pathをテストし、次にh1タグとtitleタグが正しいことをチェックしました。これを再現したものがリスト7.6です (5.3.4full_titleヘルパーについては、フルタイトルが既にテスト済みであることから省略しました)。

リスト7.6 最初に行ったUserページspecの再現。
spec/requests/user_pages_spec.rb
require 'spec_helper'

describe "User pages" do

  subject { page }

  describe "signup page" do
    before { visit signup_path }

    it { should have_content('Sign up') }
    it { should have_title(full_title('Sign up')) }
  end
end

ユーザー表示ページをテストするために、showアクションのコード (リスト7.5) で検索を行うUserモデルオブジェクトが必要となります。

describe "profile page" do
  # ユーザー変数を作成するためのコードに置き換える。
  before { visit user_path(user) }

  it { should have_content(user.name) }
  it { should have_title(user.name) }
end

これからコメントの部分に適切なコードを追加します。ここではuser_pathという名前付きルート (表7.1) を使用して、指定されたユーザーのページを表示するためのパスを生成します。これにより、ページのコンテンツとタイトルの両方にユーザーの名前が含まれているかどうかをテストできます。

必要なUserモデルオブジェクトを作成するために、Active Recordを使用してUser.createという形式でユーザーを作成することもできますが、経験上、ユーザーのオブジェクトを定義してそれをデータベースに挿入するには、ユーザーのファクトリー (factory) を使用する方がはるかに便利です。ここでは、Factory Girlを使用して生成したファクトリーを使用します。Factory Girlは、thoughtbotのメンバーが作成したRuby gemです。Factory Girlは、RSpecを使用してRubyで「ドメイン特化言語 (domain-specific language)」を定義します。ここでは、Active Recordのオブジェクトの定義に特化しています。このドメイン特化言語の文法はシンプルで、必要なオブジェクトの属性を定義するためにRubyのブロックとカスタムメソッドを使用しています。この章のサンプルアプリケーションでは、Active Recordのメリットはまだはっきりとはわからないかもしれませんが、この後の章ではファクトリーの機能をさらに活用する予定です。たとえば9.3.3では、複数のユーザーを作成し、それぞれに独自のメールアドレスを持たせる作業が重要となりますが、ファクトリーを使用することで簡単になります。

他のRuby gemと同様、Bundlerで使用するGemfileに以下のように1行追加することでFactory Girlをインストールできます (リスト7.7) (Factory Girlはテスト環境でしか使用しないので、以下のように:testグループに追加します)。

リスト7.7 GemfileにFactory Girlを追加する。
source 'https://rubygems.org'
  .
  .
  .
group :test do
  .
  .
  .
  gem 'factory_girl_rails', '4.2.1'
end
.
.
.

次に、いつものように以下を実行します。

$ bundle install

Factory Girlのファクトリーはすべてspec/factories.rbファイルに置きます。ここにあるファクトリーはRSpecによって自動的に読み込まれます。Userファクトリが見えるようにするためのコードはリスト7.8です。

リスト7.8 Userモデルオブジェクトをシミュレートするためのファクトリー。
spec/factories.rb
FactoryGirl.define do
  factory :user do
    name     "Michael Hartl"
    email    "michael@example.com"
    password "foobar"
    password_confirmation "foobar"
  end
end

シンボル:userfactoryコマンドに渡されると、Factory Girlはそれに続く定義がUserモデルオブジェクトを対象としていることを認識します。

リスト7.8の定義をしたことにより、letコマンド (コラム 6.3) とFactory GirlのFactoryGirlメソッドを使ってUserのファクトリーを作成することができるようになります。

let(:user) { FactoryGirl.create(:user) }

最終的なコードはリスト7.9のようになります。

リスト7.9 ユーザー表示ページ用のテスト。
spec/requests/user_pages_spec.rb
require 'spec_helper'

describe "User pages" do

  subject { page }

  describe "profile page" do
    let(:user) { FactoryGirl.create(:user) }
    before { visit user_path(user) }

    it { should have_content(user.name) }
    it { should have_title(user.name) }
  end

  describe "signup page" do
    before { visit signup_path }

    it { should have_content('Sign up') }
    it { should have_title(full_title('Sign up')) }
  end
end

この時点で以下を実行すると、テストスイートが赤色 (失敗) になるはずです。

$ bundle exec rspec spec/

このコードは、リスト7.10のようにすることで緑色 (成功) になります。

リスト7.10 ユーザーのプロファイルページにタイトルと見出しを追加する。
app/views/users/show.html.erb
<% provide(:title, @user.name) %>
<h1><%= @user.name %></h1>

以下のコマンドを再度実行すると、リスト7.9はパスするはずです。

$ bundle exec rspec spec/

Factory Girlを使用したテストを実行してみるとすぐに気が付くと思いますが、正直言って遅いです。 これはFactory Girlに問題があるわけではなく、あくまで機能の一部であり、バグではありません。6.3.1で使用したBCryptアルゴリズムによるセキュアパスワードのハッシュ生成そのものが仕様上遅いのが原因です。BCryptが遅いということは、裏を返せばそれだけ攻撃に強いということでもあります。残念ながら、このままではユーザーの作成をそのままテストに含めると遅くなってしまいます。幸いなことに、この問題は簡単に回避できます。bcrypt-rubyライブラリでは、セキュアハッシュを生成する際の計算の負荷をコストファクター (cost factor) として指定できます。コストファクターのデフォルト値は速度よりセキュアであることを重視しています。これは本番環境では最適ですが、テスト環境では不利です。私たちはテストを高速で行いたいのであり、ユーザーのパスワードハッシュのセキュリティについてはテスト中は気にしたくありません。解決方法は、リスト7.11に示すように、テスト設定ファイルconfig/environments/test.rbに数行追加することです。これによりコストファクターが再定義され、デフォルトのセキュリティ重視から速度重視の最小値に変わります。 かなり小規模なテストであっても、この修正によって速度は相当向上します。test.rbにはいつもリスト7.11の数行を追加しておくことをぜひともお勧めします。

リスト7.11 BCryptのコストファクターをテスト環境向けに再定義する。
config/environments/test.rb
SampleApp::Application.configure do
  .
  .
  .
  # bcrypt'のコスト関数を下げることでテストの速度を向上させる。
  ActiveModel::SecurePassword.min_cost = true
end

7.1.4gravatar画像とサイドバー

前節で基本的なユーザーページの定義は終わりましたので、今度は各ユーザーのプロファイル写真のあたりをもう少し肉付けし、サイドバーも作り始めましょう。ビューを作成するときは、ページの構造が正確であるかどうかよりもページの外観の方を重要視するのが普通なので、少なくとも今はビューをテストする必要はありません。ページネーション (9.3.3) のような、エラーの起きやすい要素をビューで使用するようになったら、ビューでもテスト駆動開発を行います。

ここではGravatar (globally recognized avatar) をユーザープロファイルに導入してみましょう6。GravatarはGitHubの共同設立者であるTom Preston-Wernerによって開発され、後にAutomattic (WordPressの作者) に買い取られました。Gravatarは無料のサービスで、プロファイル写真をアップロードして、指定したメールアドレスと関連付けることができます。Gravatarは、プロファイル写真をアップロードするときの面倒な作業や、写真が欠けたりなどのトラブル、置き場所の悩みを解決します。ユーザーのメールアドレスを組み込んだGravatar専用の画像パスを構成するだけで、対応するGravatarの画像が自動的に表示されます7

ここでは、リスト7.12のようにgravatar_forヘルパーメソッドを使用してGravatarの画像を利用できるようにします。

リスト7.12 ユーザー表示ビューに名前とGravatarを表示する。
app/views/users/show.html.erb
<% provide(:title, @user.name) %>
<h1>
  <%= gravatar_for @user %>
  <%= @user.name %>
</h1>

この時点では、以下のテストスイートは失敗するはずです。

$ bundle exec rspec spec/

gravatar_forメソッドが未定義のため、ユーザー表示ビューは現在動作していません。(このようなエラーを捉えることができるのが、ビューでSpecsを使用する大きなメリットです。だからこそ、どんなに小規模であってもよいのでビューで何らかのテストを行っておくことが重要なのです。)

デフォルトでは、ヘルパーファイルで定義されているメソッドは自動的にすべてのビューで利用できます。ここでは、利便性を考えてgravatar_forをUsersコントローラに関連付けられているヘルパーファイルに置くことにしましょう。Gravatarのホームページにも書かれているように、GravatarのURLはMD5ハッシュを用いてユーザーのメールアドレスをハッシュ化しています。Rubyでは、Digestライブラリのhexdigestメソッドを使用したMD5ハッシュアルゴリズムが実装されています。

>> email = "MHARTL@example.COM".
>> Digest::MD5::hexdigest(email.downcase)
=> "1fda4469bcbec3badf5418269ffc5968" 

メールアドレスは大文字と小文字を区別しませんが (6.2.4)、MD5ハッシュでは大文字と小文字が区別されるので、Rubyのdowncaseメソッドを使用してhexdigestの引数を小文字に変換しています。gravatar_forヘルパーを組み込んだ結果をリスト7.13に示しました。

リスト7.13 gravatar_forヘルパーメソッドを定義する。
app/helpers/users_helper.rb
module UsersHelper

  # 与えられたユーザーのGravatar (http://gravatar.com/) を返す。
  def gravatar_for(user)
    gravatar_id = Digest::MD5::hexdigest(user.email.downcase)
    gravatar_url = "https://secure.gravatar.com/avatar/#{gravatar_id}"
    image_tag(gravatar_url, alt: user.name, class: "gravatar")
  end
end

リスト7.13のコードは、Gravatarの画像タグに"gravatar"クラスとユーザー名のaltテキストを追加したものを返します (altテキストを追加しておくと、視覚障害のあるユーザーがスクリーンリーダーを使用するときにも役に立ちます)。今度は以下のテストスイートは成功するはずです。

$ bundle exec rspec spec/

プロファイルページは図7.7のようになります。ここにはデフォルトのGravatar画像が表示されていますが、これはデフォルトのメールアドレスuser@example.comが有効ではないためです (example.comというドメイン名は、例として使用するために特別に予約されています)。

profile_with_gravatar_bootstrap_4_0
図7.7ユーザープロファイルページ/users/1にデフォルトのGravatarが表示されている。(拡大)

アプリケーションでカスタムGravatarを利用できるようにするために、update_attributes (6.1.5) を使用してデータベース上のユーザー情報を更新します。

$ rails console
>> user = User.first
>> user.update_attributes(name: "Example User",
?>                        email: "example@railstutorial.org",
?>                        password: "foobar",
?>                        password_confirmation: "foobar")
=> true

ここではユーザーのメールアドレスに example@railstutorial.org を使用しました (図7.8)。このメールアドレスはRailsチュートリアルのロゴでも使用されています。

profile_custom_gravatar_bootstrap_4_0
図7.8ユーザー表示ページにカスタムのGravatarを表示する。(拡大)

図7.1のモックアップに近づけるために、ユーザーのサイドバーの最初のバージョンを作りましょう。ここではasideタグを使用して実装します。このタグはサイドバーなどの補完コンテンツの表示に使用されますが、単独で表示することもできます。rowクラスとspan4クラスも追加しておきます。これらのクラスはBootstrapの一部です。ユーザー表示ページを変更した結果をリスト7.14に示します。

リスト7.14 ユーザーのshowビューにサイドバーを追加する。
app/views/users/show.html.erb
<% provide(:title, @user.name) %>
<div class="row">
  <aside class="span4">
    <section>
      <h1>
        <%= gravatar_for @user %>
        <%= @user.name %>
      </h1>
    </section>
  </aside>
</div>

HTML要素とCSSクラスを配置したことにより、プロファイルページ (とサイドバーとGravatar) にSCSSでリスト7.15のようにスタイルを与えることができるようになりました (テーブルCSSのルールがネスティング (入れ子) されていますが、これが有効になるのはAsset PipelineでSassエンジンが使用されている場合に限られます) 。ページの変更の結果を図7.9に示します。

リスト7.15 SCSSを使用してサイドバーなどのユーザー表示ページにスタイルを与える。
app/assets/stylesheets/custom.css.scss
.
.
.

/* sidebar */

aside {
  section {
    padding: 10px 0;
    border-top: 1px solid $grayLighter;
    &:first-child {
      border: 0;
      padding-top: 0;
    }
    span {
      display: block;
      margin-bottom: 3px;
      line-height: 1;
    }
    h1 {
      font-size: 1.4em;
      text-align: left;
      letter-spacing: -1px;
      margin-bottom: 3px;
      margin-top: 0px;
    }
  }
}

.gravatar {
  float: left;
  margin-right: 10px;
}
user_show_sidebar_css_bootstrap
図7.9ユーザー表示ページ/users/1にサイドバーとCSSを追加する。(拡大)

7.2ユーザー登録フォーム

ここまででユーザープロファイルページがひとまず動作するようになりましたので、今度はユーザー登録フォームを作成しましょう。図5.9 (図7.10にも再録) に示したとおり、ユーザー登録ページはまだ空白のままなので、このままではユーザー登録できません。この節の目標は、このみっともないページを改造して図7.11のモックアップのようなページに変えることです。

new_signup_page_bootstrap
図7.10現状のユーザー登録ページ /signup(拡大)
signup_mockup_bootstrap
図7.11ユーザー登録ページのモックアップ。(拡大)

Web経由でユーザーを作成する機能をこれから追加しますので、6.3.5で作成したユーザーをここで削除しておきましょう。 最も簡単な方法は、Rakeのdb:resetタスクを実行してデータベースをリセットすることです。

$ bundle exec rake db:reset

システム環境によっては、データベースをリセットした後にもう一度prepareを実行しておく必要が生じることがあります。

$ bundle exec rake db:test:prepare

最後に、システムによっては変更を反映するためにターミナルでCtrl-Cを押してWebサーバーを再起動する必要が生じることもあります8

7.2.1ユーザー登録のためのテスト

完全なテスト機能を備えた強力なWebフレームワークがなかった頃は、テスティング作業は苦痛に満ち、しばしばそこでエラーが発生しました。たとえば、もし仮にユーザー登録ページを手動でテストしなければならないとしたら、ブラウザでそのページを表示し、有効なデータと無効なデータを交互に流しこみ、どちらの場合にもアプリケーションが正常に動作することを確認しなければならないでしょう。さらに、アプリケーションに変更が生じるたびに、まったく同じテストを繰り返さなければなりません。RSpecとCapybaraを使用することで、柔軟性の高いテストを作成できるようになり、従来手動で行うしかなかったこれらのテストを自動化できるようになりました。

私たちは、既にCapybaraがWeb操作の文法を直感的にサポートしているところを目の当たりにしてきました。これまでは、特定のページを参照するときにvisitを使用することがほとんどでしたが、Capybaraの能力はそれだけにとどまりません。図7.11のフィールドに文字列を入力したり、ボタンをクリックしたりすることもできます。Capybaraの文法は以下のような感じです。

visit signup_path
fill_in "Name", with: "Example User"
.
.
.
click_button "Create my account"

目標は、正しくないユーザー登録情報と正しいユーザー登録情報を与えたときに、期待どおりに動作することを確認するテストの作成です。このテストはかなり込み入っているので、1つ1つ作り上げていきましょう。テストの動作結果や、どこにテストを置けばよいかなどをとにかく参照したい場合は、リスト7.16までスキップしてください。

最初のタスクは、ユーザー登録フォームの表示が失敗しないかどうか、そしてページを表示し、ボタンを押し、無効なデータを送信する動作をシミュレートすることです。ボタンを代わりに押すにはclick_buttonを使用します。

visit signup_path
click_button "Create my account"

このテストは、ユーザー登録ページをブラウザで表示し、ユーザー登録情報に何も入力しないまま送信する操作 (無効な操作) と同等です。同様に、有効なデータを送信する操作をシミュレートするには、 fill_inを使用して正しいユーザー情報を与えます。

visit signup_path
fill_in "Name",         with: "Example User"
fill_in "Email",        with: "user@example.com"
fill_in "Password",     with: "foobar"
fill_in "Confirmation", with: "foobar"
click_button "Create my account"

このテストの目的は、ユーザー登録ボタンを押したときに期待どおりに動作すること、ユーザー情報が有効な場合にはユーザーが新規作成され、無効な場合にはユーザーが作成されないことを確認することです。これを確認するには、ユーザーのcountを使用します。背後で動作するこのcountメソッドは、Userを含むあらゆるActive Recordクラスで使用できます。

$ rails console
>> User.count
=> 0

この節の冒頭でデータベースをリセットしてあるので、現時点ではUser.count0になっています。

無効なデータを送信した場合、ユーザーのカウントが変わらないことが期待されます。有効なデータを送信した場合には、ユーザーのカウントが1つ増えることが期待されます。このような動作は、RSpecでexpectメソッドをtoメソッドまたは not_toメソッドと組み合わせて実現できます。まずは、確認しやすい無効な場合についてやってみましょう。ユーザー登録ページのパスをブラウザで開いてボタンをクリックしたら、ユーザーアカウントが変更されないという動作です。

visit signup_path
expect { click_button "Create my account" }.not_to change(User, :count)

ここで、expectclick_buttonを中括弧で囲んでブロックになっていることに注目してください (4.3.2)。これはchangeメソッドで役に立ちます。このメソッドはオブジェクトとシンボルを引数に取り、シンボルを呼び出した結果を計算してオブジェクト上のメソッドとします。これはブロックの前と後いずれについても行われます。つまり、以下のコードは

expect { click_button "Create my account" }.not_to change(User, :count)

以下を計算します。

User.count

上の計算は、以下の実行前と実行後の両方で行われます。

click_button "Create my account"

この場合は、コードがカウントを変更しないことが期待されますので、not_toメソッドで表現しています。実際には、以下の同等のコードを

initial = User.count
click_button "Create my account"
final = User.count
expect(initial).to eq final

ボタンクリックをブロックで囲むことによって以下のように1行で表しています。

expect { click_button "Create my account" }.not_to change(User, :count)

こうすることで英語に近い自然な表記が可能になり、さらにコンパクトになります (eqは同値性をテストするRSpecのメソッドです)。

データが有効な場合のテストも大きな違いはありませんが、今度はカウントが更新されないのではなく、カウントが1つ増えることを確認します。

visit signup_path
fill_in "Name",         with: "Example User"
fill_in "Email",        with: "user@example.com"
fill_in "Password",     with: "foobar"
fill_in "Confirmation", with: "foobar"
expect do
  click_button "Create my account"
end.to change(User, :count).by(1)

ここではtoメソッドを使用して、正しいデータを与えてユーザー登録ボタンを押したときにカウントが1つ増えることを確認します。

上の2つのテストを適切なdescribeブロックで囲い、両者に共通する部分をbeforeブロックに移動すると、リスト7.16のようにユーザー登録を正しくテストできる基本的なテストのできあがりです。ここでは、送信ボタン用の共通部分を分解するためにletメソッドを使用してsubmit変数を定義しています。

リスト7.16 ユーザー登録の基本的なテスト。
spec/requests/user_pages_spec.rb
require 'spec_helper'

describe "User pages" do

  subject { page }
  .
  .
  .
  describe "signup" do

    before { visit signup_path }

    let(:submit) { "Create my account" }

    describe "with invalid information" do
      it "should not create a user" do
        expect { click_button submit }.not_to change(User, :count)
      end
    end

    describe "with valid information" do
      before do
        fill_in "Name",         with: "Example User"
        fill_in "Email",        with: "user@example.com"
        fill_in "Password",     with: "foobar"
        fill_in "Confirmation", with: "foobar"
      end

      it "should create a user" do
        expect { click_button submit }.to change(User, :count).by(1)
      end
    end
  end
end

この節では、他にもいくつかのテストを追加する予定ですが、リスト7.16の基本テストだけでも既に相当量の機能をカバーしています。テストにパスするには、ユーザー登録ページを正しいHTML要素で作成し、ページの送信が正しい場所へルーティングされるようにし、ユーザーデータが正しい場合にのみ新しいユーザーを作成できるようにする必要があります。

もちろん、今の時点ではテストは失敗します。

$ bundle exec rspec spec/

7.2.2form_forを使用する

テストが正しく失敗したので、今度はユーザー登録のフォームを作成してテストにパスするようにしましょう。これを行うには、Railsでform_forヘルパーメソッドを使用します。このメソッドはActive Recordオブジェクトを取り込み、オブジェクトの属性を使用してフォームを構成します。変更の結果をリスト7.17に示します (Rails 2.xに慣れている方は、form_forではコンテンツを挿入するときに “%=” を使用するERb方式の文法を使用していることに注意してください。Rails 2.xでは<% form_for ... %>を使用していましたが、現在は代わりに<%= form_for ... %>を使用します)。

リスト7.17 新しいユーザーを登録するフォーム。
app/views/users/new.html.erb
<% provide(:title, 'Sign up') %>
<h1>Sign up</h1>

<div class="row">
  <div class="span6 offset3">
    <%= form_for(@user) do |f| %>

      <%= f.label :name %>
      <%= f.text_field :name %>

      <%= f.label :email %>
      <%= f.text_field :email %>

      <%= f.label :password %>
      <%= f.password_field :password %>

      <%= f.label :password_confirmation, "Confirmation" %>
      <%= f.password_field :password_confirmation %>

      <%= f.submit "Create my account", class: "btn btn-large btn-primary" %>
    <% end %>
  </div>
</div>

このフォームの各部分について見てみましょう。doキーワードは、 form_forが1つの変数を持つブロックを取ることを表します。この変数fは “form” のfです。

<%= form_for(@user) do |f| %>
  .
  .
  .
<% end %>

通常、Railsヘルパーを使用している場合、実装の詳細について知っておく必要はありません。ただしfというオブジェクトが何をするのかは知っておく必要があります。 HTMLフォーム要素 (テキストフィールド、ラジオボタン、パスワードフィールドなど) に対応するメソッドが呼び出されると、@user オブジェクトの属性を設定するように特別に設計された要素のためのコードを返します。つまり、以下のコードを実行すると、

<%= f.label :name %>
<%= f.text_field :name %>

Userモデルのname属性を設定する、ラベル付きテキストフィールド要素を作成するのに必要なHTMLを作成します (HTML自体については7.2.3で詳しく説明します)。

実際の動作を理解するには、このフォームによって生成される実際のHTMLについて理解を深める必要があります。しかしここで問題があります。このページでは@user変数に何も設定されておらず、未完成です。 他のすべての未定義インスタンス変数 (4.4.5) と同様、@userの内容は現時点ではnilです。従って、この時点でテストスイートを実行すると、リスト7.6のユーザー登録ページのコンテンツに対するテストは失敗します。

$ bundle exec rspec spec/requests/user_pages_spec.rb -e "signup page"

(この-eオプションは、descriptionの文字列が"signup page"に一致する例を単に実行するためのものです。ここで注意していただきたいのは、これは "signup" という部分文字列とは違うということです。もし"signup" であればリスト7.16のすべてのテストが実行されるでしょう。) テストにパスしてフォームが正常に出力されるようにするには、new.html.erbに対応する@user変数をコントローラのアクションで定義する必要があります。たとえば、Usersコントローラのnewがそうです。form_forヘルパーは@userがUserオブジェクトであることを前提としています。ここでは新しいユーザーを作成するので、リスト7.18のように単にUser.newとします。

リスト7.18 @user変数をnewアクションに追加する。
app/controllers/users_controller.rb
class UsersController < ApplicationController
  .
  .
  .
  def new
    @user = User.new
  end
end

@user変数が上記のように定義されれば、以下のユーザー登録ページのテストはパスするはずです (訳注: 以下のコマンドでは "signup page" のテストだけを行なっています)。

$ bundle exec rspec spec/requests/user_pages_spec.rb -e "signup page"

この時点で、フォーム にリスト7.19のようにスタイルが与えられていれば、図7.12のように表示されます。box_sizingミックスインをリスト7.2から再利用していることに注目してください。

リスト7.19 ユーザー登録フォームのCSS。
app/assets/stylesheets/custom.css.scss
.
.
.

/* forms */

input, textarea, select, .uneditable-input {
  border: 1px solid #bbb;
  width: 100%;
  padding: 10px;
  margin-bottom: 15px;
  @include box_sizing;
}

input {
  height: auto !important;
}
signup_form_bootstrap
図7.12新規ユーザーのためのユーザー登録フォーム/signup(拡大)

7.2.3フォームHTML

図7.12に示したように、ユーザー登録は正常に表示されるようになりました。これは、リスト7.17form_forコードが正しいHTMLを生成しているということです。生成されたフォームのHTMLソースを見てみると (Firebugを使うか、ブラウザの [ソースを表示] を使用)、ソースのマークアップはリスト7.20のようになっているはずです。このHTMLの細かい部分はほとんど私たちの目的には関係ありませんが、この構造の最も重要な部分に注目してみましょう。

リスト7.20 図7.12のフォームのHTMLソース。
<form accept-charset="UTF-8" action="/users" class="new_user"
      id="new_user" method="post">

  <label for="user_name">Name</label>
  <input id="user_name" name="user[name]" type="text" />

  <label for="user_email">Email</label>
  <input id="user_email" name="user[email]" type="text" />

  <label for="user_password">Password</label>
  <input id="user_password" name="user[password]"
         type="password" />

  <label for="user_password_confirmation">Confirmation</label>
  <input id="user_password_confirmation"
         name="user[password_confirmation]" type="password" />

  <input class="btn btn-large btn-primary" name="commit" type="submit"
         value="Create my account" />
</form>

(実は、このソースからは信頼性トークン (authenticity token) 関連のHTMLを除外してあります。Railsは、ある種のクロスサイトリクエスト偽造 (CSRF: cross-site request forgery) 攻撃に対抗するためにこのようなHTMLを自動的に追加します。動作の詳細や、この防御が重要な理由を知りたい場合は、Stack OverflowのRails信頼性トークン関連の書き込み (英語) を参照してください。

まずはこのHTMLソースの内部構造について説明します。リスト7.17リスト7.20をじっくり見比べてみると、以下の埋め込みRubyは

<%= f.label :name %>
<%= f.text_field :name %>

以下のHTMLを生成していることがわかります。

<label for="user_name">Name</label>
<input id="user_name" name="user[name]" type="text" />

さらに、以下のコードは

<%= f.label :password %>
<%= f.password_field :password %>

以下のHTMLを生成していることがわかります。

<label for="user_password">Password</label><br />
<input id="user_password" name="user[password]" type="password" />

次の図7.13に示すように、テキストフィールド (type="text") では内容をそのまま表示していますが、パスワードフィールド (type="password") ではセキュリティ上の目的のために文字が隠蔽されています。

filled_in_form_bootstrap
図7.13textフィールドとpasswordフィールドに文字を入力した状態。(拡大)

7.4でも説明しますが、ユーザーの作成で重要なのはinputごとにある特殊なname属性です。

<input id="user_name" name="user[name]" ... />
.
.
.
<input id="user_password" name="user[password]" ... />

Railsはこれらのnameの値を使用して、初期化ハッシュを (params変数経由で) 構成します。このハッシュは、ユーザーが入力した値に基づいてユーザーを作成するとき (7.3) に使用されます。

次に重要な要素は、formタグ自身です。Railsはformタグを作成するときに@userオブジェクトを使用します。すべてのRubyオブジェクトは自分のクラスを知っている (4.4.1) ので、Railsは@userのクラスがUserであることを知ることができます。また、@user新しいユーザーなので、 Railsはpostメソッドを使用してフォームを構成する方法を知っています。これは新しいオブジェクトを作成するための正式な動詞 (verb) です (コラム 3.3)。

<form action="/users" class="new_user" id="new_user" method="post">

ここでclassid属性はほぼ無関係です。ここで重要な属性はaction="/users"method="post"です。これらの2つの属性は、/usersというURLへのHTTP POSTリクエストに対する指示を構成しています。これらの属性の効用については次の2つの節で説明します。

7.3ユーザー登録失敗

図7.12ではフォームのHTMLがどうなっているかを簡単に説明しました (リスト7.20参照) が、フォームを理解するにはユーザー登録の失敗のときが最も参考になります。この節では、無効なデータ送信を受け付けるユーザー登録フォームを作成し、ユーザー登録フォームを更新してエラーの一覧を表示します。このモックアップを図7.14に示します。

signup_failure_mockup_bootstrap
図7.14ユーザー登録が失敗したときのモックアップ。(拡大)

7.3.1正しいフォーム

7.1.2で、resources :usersroutes.rbファイルに追加すると (リスト7.3) 自動的にRailsアプリケーションが表7.1のRESTful URI に応答するようになったことを思い出してください。特に、/usersへのPOSTリクエストはcreateアクションに送られます。私たちはここで、createアクションでフォーム送信を受け取り、User.newを使用して新しいユーザーオブジェクトを作成し、ユーザーを保存 (または保存に失敗) し、再度の送信用のユーザー登録ページを表示するという方法で機能を実装しようと思います。まずはユーザー登録フォームのコードを見直してみましょう。

<form action="/users" class="new_user" id="new_user" method="post">

7.2.3で説明したように、このHTMLはPOSTリクエストを /usersというURLに送信します。

リスト7.21のコードがテストにパスするようにするためには、最初にリスト7.16にある無効な情報をテストします。このリストでは、5.1.3の「パーシャル」のところでも使ったrenderメソッドを再度使いまわしています。renderはコントローラのアクションの中でも正常に動作します。ここで、以前に説明したif-else分岐構造を思い出してください。この文を使用して、保存が成功したかどうかに応じて@user.saveの値がtrueまたはfalse (6.1.3) になるときに、それぞれ成功時の処理と失敗時の処理を場合分けすることができます。

リスト7.21 ユーザー登録の失敗に対応できるcreateアクション。
app/controllers/users_controller.rb
class UsersController < ApplicationController
  .
  .
  .
  def create
    @user = User.new(params[:user])    # 実装は終わっていないことに注意!
    if @user.save
      # 保存の成功をここで扱う。
    else
      render 'new'
    end
  end
end

コメントにもあるように、上のコードはまだ実装が完了していませんので注意してください。しかし実装の出発点としてはこれで十分です。実装は7.3.2で完了する予定です。

リスト7.21のコードの動作を理解するもっともよい方法は、実際に無効なユーザー登録データを送信 (submit)してみることです。結果を図7.15に、完全なデバッグ情報を図7.16に示しました。

signup_failure_rails_4
図7.15ユーザー登録失敗。(拡大)
signup_failure_rails_4_debug
図7.16ユーザー登録失敗時のデバッグ情報。(拡大)

Railsが送信を扱う方法をより深く理解するために、デバッグ情報のうちパラメーターハッシュのuserの部分を詳しく見てみましょう (図7.16)。

"user" => { "name" => "Foo Bar",
            "email" => "foo@invalid",
            "password" => "[FILTERED]",
            "password_confirmation" => "[FILTERED]"
          }

このハッシュはUsersコントローラにparamsとして渡されます。7.1.2で説明したとおり、このparamsハッシュには各リクエストの情報が含まれています。/users/1のようなURLの場合、params[:id]の値は該当するユーザーのid (この例では1) になります。ユーザー登録情報の送信の場合、paramsには複数のハッシュに対するハッシュ (hash-of-hashes: 入れ子になったハッシュ) が含まれます (なお、4.3.3ではhash-of-hashesの説明とともに、コンソールセッションで使用するためにあえてparamsという名前の変数を導入しました)。上のデバッグ情報では、フォーム送信の結果が、送信された値に対応する属性とともにuserハッシュに保存されています。ハッシュのキーは、inputタグのname属性です (リスト7.17)。

<input id="user_email" name="user[email]" type="text" />

"user[email]"という名前は、userハッシュのemail属性を正確に指します。

ハッシュのキーはデバッグ情報では文字列として表現されていますが、Railsはこれらを文字列ではなく、params[:user]がuser属性のハッシュになるような「シンボル」(Rubyの機能の1つで、一意性が保証された特別な文字列) としてUsersコントローラに渡します。実際、4.4.5で初めて使われ、リスト7.21でも使用されていたように、これらのハッシュはUser.newの引数として必要な属性と正確に一致します。これはつまり、以下の行は

@user = User.new(params[:user])

以下とほぼ等価であるということです。

@user = User.new(name: "Foo Bar", email: "foo@invalid",
                 password: "foo", password_confirmation: "bar")

以前のバージョンのRailsでは、以下のコードは

@user = User.new(params[:user])

実際に動作しましたが、動作はデフォルトで不安定であり、悪意のあるユーザーによってアプリケーションのデータベースが書き換えられることのないように慎重な手続きによって使用しなければならず、しかもその手続はエラーを起こしやすいものでした。Rails 4.0では、上のコードはエラーになります (上の図7.15および図7.16を参照)。これにより、デフォルトでセキュリティが高められました。関連する以下のテストが失敗することを確認することで、このことをダブルチェックできます。

$ bundle exec rspec spec/requests/user_pages_spec.rb \
-e "signup with invalid information"

7.3.2 Strong Parameters

4.4.5で、マスアサインメントの概念について簡単に説明しました。これは、以下のように値のハッシュを使用してRubyの変数を初期化するものです。

@user = User.new(params[:user])    # 実装は終わっていないことに注意!

リスト7.21のコメントと、上の再録コメントでも重ねて指摘しているように、この実装は最終形ではありません。その理由は、paramsハッシュ全体を初期化するという行為はセキュリティ上、極めて危険だからです。これは、ユーザーが送信したデータをまるごとUser.newに渡していることになります。ここで、Userモデルにadmin属性というものがあるとしましょう。この属性は、Webサイトの管理者であるかどうかを示します (この属性を実装するのは9.4.1になってからです)。admin=’1’という値をparams[:user]の一部に紛れ込ませて渡してしまえば、この属性をtrueにすることができます。これはcurlなどのコマンドベースのHTTPクライアントを使用すれば簡単に行うことができます。paramsハッシュがまるごとUser.newに渡されてしまうと、どのユーザーでもadmin=’1’をWebリクエストに紛れ込ませるだけでWebサイトの管理者権限を奪い取ることができてしまいます。

以前のバージョンのRailsでは、モデル層でattr_accessibleメソッドを使用することで上のような危険を防止していましたが、 Rails 4.0ではコントローラ層でStrong Parametersというテクニックを使用することが推奨されています。Strong Parametersを使用することで、必須のパラメータと許可されたパラメータを指定することができます。さらに、上のようにparamsハッシュをまるごと渡すとエラーが発生するので、Railsはデフォルトでマスアサインメントの脆弱性から守られるようになりました。

この場合、paramsハッシュでは:user属性を必須とし、名前、メールアドレス、パスワード、パスワードの確認の属性をそれぞれ許可し、それ以外を許可しないようにしたいと考えています。これは、以下のように記述することで行うことができます。

params.require(:user).permit(:name, :email, :password, :password_confirmation)

このコードの戻り値は、paramsハッシュのバージョンと、許可された属性です (:user属性がない場合はエラーになります)。

これらのパラメータを使いやすくするために、user_paramsという外部メソッドを使用するのが慣習になっています。このメソッドは適切な初期化ハッシュを返します。このメソッドはUsersコントローラの内部でのみ実行され、Web経由で外部ユーザーにさらされる必要はないため、リスト7.22に示すようにRubyのprivateキーワードを使用してprivateに設定します (privateキーワードの詳細については 8.2.1で説明します)。

リスト7.22 createアクションでStrong Parametersを使用する。
app/controllers/users_controller.rb
class UsersController < ApplicationController
  .
  .
  .
  def create
    @user = User.new(user_params)
    if @user.save
      # 保存の成功をここで扱う。
    else
      render 'new'
    end
  end

  private

    def user_params
      params.require(:user).permit(:name, :email, :password,
                                   :password_confirmation)
    end
end

リスト7.22のコードを使用することで、無効な送信のテストはパスするはずです。

$ bundle exec rspec spec/requests/user_pages_spec.rb \
-e "signup with invalid information"

7.3.3ユーザー登録のエラーメッセージ

ユーザー登録に失敗した場合の最後の手順として、問題が生じたためにユーザー登録が行われなかったということをユーザーにわかりやすく伝えるエラーメッセージを追加しましょう。Railsは、このようなメッセージをUserモデルの検証時に自動的に生成してくれます。たとえば、ユーザー情報のメールアドレスが無効で、パスワードが短すぎる状態で保存しようとしたとします。

$ rails console
>> user = User.new(name: "Foo Bar", email: "foo@invalid",
?>                 password: "dude", password_confirmation: "dude")
>> user.save
=> false
>> user.errors.full_messages
=> ["Email is invalid", "Password is too short (minimum is 6 characters)"]

6.2.2でも簡単に説明しましたが、errors.full_messagesオブジェクトにはエラーメッセージの配列が含まれています。

上のコンソールセッションに示されているように、リスト7.21で保存に失敗すると、@userオブジェクトに関連付けられたエラーメッセージの一覧が生成されます。このメッセージをブラウザで表示するには、ユーザーのnewページでエラーメッセージのパーシャル (partial) を出力します。リスト7.23に示します。(このエラーメッセージのテストを行うのはよいことですが、演習の課題としてあえて残しておきます。7.6を参照してください。) ここで使用しているエラーメッセージのパーシャルはあくまで試作品である点に注意してください。最終版は10.3.2を参照してください。

リスト7.23 ユーザー登録フォームでエラーメッセージを表示するコード。
app/views/users/new.html.erb
<% provide(:title, 'Sign up') %>
<h1>Sign up</h1>

<div class="row">
  <div class="span6 offset3">
    <%= form_for(@user) do |f| %>
      <%= render 'shared/error_messages' %>
      .
      .
      .
    <% end %>
  </div>
</div>

ここでは、’shared/error_messages’というパーシャルをrender (レンダリング) している点に注目してください。これはRails全般の慣習で、パーシャルは複数のコントローラにわたるビューに対し、専用のshared/ディレクトリを使用するようにしています (これは9.1.1で実現します)。 つまり、app/views/sharedディレクトリと_error_messages.html.erbパーシャルファイルの両方を作成する必要があるということです。パーシャル自身の内容をリスト7.24に示します。

リスト7.24 フォーム送信のエラーメッセージを表示するパーシャル。
app/views/shared/_error_messages.html.erb
<% if @user.errors.any? %>
  <div id="error_explanation">
    <div class="alert alert-error">
      The form contains <%= pluralize(@user.errors.count, "error") %>.
    </div>
    <ul>
    <% @user.errors.full_messages.each do |msg| %>
      <li>* <%= msg %></li>
    <% end %>
    </ul>
  </div>
<% end %>

パーシャルによって、RailsとRubyには、Railsエラーオブジェクト用の2つのメソッドを含む多くの成果物が導入されました。最初はcountメソッドを紹介します。これはエラーの数を返します。

>> user.errors.count
=> 2

もう1つはany?メソッドです。これはempty?メソッドと互いに補完します。

>> user.errors.empty?
=> false
>> user.errors.any?
=> true

4.2.3では文字列に対してempty?メソッドを使用しましたが、Railsのエラーオブジェクトに対しても使用できます。オブジェクトが空の場合はtrue、 それ以外の場合はfalseを返します。any?メソッドはちょうどempty?と逆の動作で、要素が1つでもある場合はtrue、ない場合はfalseを返します (なお、これらのcountempty?any?メソッドは、Rubyの配列に対してもそのまま使用できます。これは10.2で応用する予定です)。

さらに、pluralizeという英語専用のテキストヘルパーが新たに登場しています。このヘルパーはデフォルトではコンソールでは使用できませんが、ActionView::Helpers::TextHelperを経由して明示的にインクルードできます9

>> include ActionView::Helpers::TextHelper
>> pluralize(1, "error")
=> "1 error"
>> pluralize(5, "error")
=> "5 errors"

pluralizeの最初の引数に整数が与えられると、それに基づいて2番目の引数の英単語を複数形に変更したものを返します。このメソッドの背後には強力なインフレクター (活用形生成) があり、不規則活用を含むさまざまな単語を複数形にすることができます。

>> pluralize(2, "woman")
=> "2 women"
>> pluralize(3, "erratum")
=> "3 errata"

pluralizeを使用することで、コードは以下のようになります。

<%= pluralize(@user.errors.count, "error") %>

このコードはたとえば "0 errors""1 error""2 errors" などのように、エラーの数に応じて活用された単語を返します。これにより、"1 errors" のような英語の文法に合わない文字列を避けることができます (これはteh interwebsにも載っている、どうしようもないほどよく見かけるエラーです)。

リスト7.24には、エラーメッセージにスタイルを与えるためのCSS id error_explanationも含まれていることに注目してください (5.1.2のCSSでスタイルidに「#」記号を使用していることも思い出してください)。さらにRailsは、エラーページにある、divで囲まれたエラーCSSクラスfield_with_errorsを適用しています。これらのラベルによって、リスト7.25のようにエラーメッセージをSCSSで整形することができます。ここでは、Sassの@extend関数を使用してBootstrapのcontrol-grouperrorの2つのクラスの機能をインクルードしています。その結果、送信失敗時のエラーメッセージは図7.17のように赤の背景で囲まれるようになります。これらのメッセージはモデルの検証時に生成されるので、メールアドレスのスタイルやパスワードの最小文字列などを変更すると、メッセージも自動的に変更されます。

リスト7.25 エラーメッセージにスタイルを与えるためのCSS。
app/assets/stylesheets/custom.css.scss
.
.
.

/* forms */
.
.
.
#error_explanation {
  color: #f00;
  ul {
    list-style: none;
    margin: 0 0 18px 0;
  }
}

.field_with_errors {
  @extend .control-group;
  @extend .error;
}
signup_error_messages_bootstrap
図7.17ユーザー登録失敗時のエラーメッセージ。(拡大)

この節で達成した成果を確認するために、リスト7.16のユーザー登録失敗のテストを再度実行し、ユーザー登録ページを表示して入力フィールドが空白のまま [Create my account] をクリックするという動作をテストしましょう。 テストの結果を図7.18に示します。今度は期待どおりテストにパスするはずです。

$ bundle exec rspec spec/requests/user_pages_spec.rb \
> -e "signup with invalid information"
blank_signup_password_digest_bootstrap_4_0
図7.18/signupを表示して何も入力せずに [Create my account] をクリックする。(拡大)

7.4ユーザー登録成功

無効なフォームの送信を扱えるようになったので、いよいよ新規ユーザーを実際にデータベースに保存できるようにし (もちろんフォームが有効な場合に)、ユーザー登録フォームを完成させましょう。まずは、ユーザーを保存できるようにします。保存に成功すると、ユーザー情報は自動的にデータベースに登録されます。次にブラウザの表示をリダイレクトして、登録されたユーザーのプロファイルを表示します。ついでにウェルカムメッセージも表示しましょう。モックアップを図7.19に示します。保存に失敗した場合は、単に7.3で開発したとおりの動作が実行されます。

signup_success_mockup_bootstrap
図7.19ユーザー登録に成功した画面のモックアップ。(拡大)

7.4.1登録フォームの完成

ユーザー登録フォームを完成させるために、リスト7.21のコメントアウトされた部分にコードを書き、適切に動作するようにしましょう。現時点では、以下の有効な送信テストは失敗するはずです。

$ bundle exec rspec spec/requests/user_pages_spec.rb \
> -e "signup with valid information"

Railsのデフォルトのアクションは対応するビューを表示するようになっていますが、createアクションに対応するビューのテンプレートがない (あるはずがありません) ため、テストに失敗します。ここでは、登録後に別のページを表示するようにし、そのページが新規作成されたユーザープロファイルであることがわかるようにします。正しいページが出力されているかどうかのテストは、演習に回すことにします (7.6)。アプリケーションのコードをリスト7.26に示します。

7.26 保存とリダイレクトを行う、userのcreateアクション。
app/controllers/users_controller.rb
class UsersController < ApplicationController
  .
  .
  .
  def create
    @user = User.new(user_params)
    if @user.save
      redirect_to @user
    else
      render 'new'
    end
  end

  private

    def user_params
      params.require(:user).permit(:name, :email, :password,
                                   :password_confirmation)
    end
end

リダイレクトではuser_urlを省略し、単にredirect_to @userと書けばユーザー表示ページに移動します。

リスト7.26のコードを使用することで、ユーザー登録ページは完成し、動作するようになります。以下のテストを実行することもできます。

$ bundle exec rspec spec/

7.4.2flash

いよいよブラウザで正しいユーザー情報を登録できるようになりましたが、その前にWebアプリケーションに常識的に備わっている機能を追加してみましょう。登録完了後に表示されるページにメッセージを表示し (この場合は新規ユーザーへのウェルカムメッセージ)、2度目以降にはそのページにメッセージを表示しないようにするというものです。Railsでは、こういう場合にflashという特殊な変数を使用できます。この変数はハッシュのように扱うことができます。4.3.3でコンソール上で実行した例を思い出してみてください。そこではあえてflashと名付けたハッシュを使用してハッシュの値を列挙しました。

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

リスト7.27のようにアプリケーションのレイアウトに含めれば、flash変数の内容をWebサイト全体にわたって表示できるようにすることもできます (このコードは、HTMLとERbがミックスされていて美しくありません。コードの改良については7.6で解説します)。

リスト7.27 flash変数の内容をWebサイトのレイアウトに追加する。
app/views/layouts/application.html.erb
<!DOCTYPE html>
<html>
  .
  .
  .
  <body>
    <%= render 'layouts/header' %>
    <div class="container">
      <% flash.each do |key, value| %>
        <div class="alert alert-<%= key %>"><%= value %></div>
      <% end %>
      <%= yield %>
      <%= render 'layouts/footer' %>
      <%= debug(params) if Rails.env.development? %>
    </div>
    .
    .
    .
  </body>
</html>

リスト7.27のコードは、flashの各要素にdivタグを追加して整形し、CSSクラスを指定してメッセージの種類がわかるようにしています。たとえば、flash[:success] = "Welcome to the Sample App!"とする場合、以下のコードを実行すると

<% flash.each do |key, value| %>
  <div class="alert alert-<%= key %>"><%= value %></div>
<% end %>

以下のHTMLが生成されます。

<div class="alert alert-success">Welcome to the Sample App!</div>

(ハッシュの:successキーはシンボルである点に注目してください。Rubyはシンボルを自動的に "success" という文字列に変換してからテンプレートに挿入します。) 使用するすべてのキーと値を列挙する理由は、他のフラッシュメッセージも使えるようにするためです。たとえば、8.1.5ではflash[:error]を使用してサインインに失敗したことを表すメッセージを表示します10

フラッシュメッセージが正しく表示されているかどうかのテストは演習に回します (7.6)。リスト7.28のように、createアクションでflash[:success]にウェルカムメッセージを割り当てれば 、テストにパスするようになります。

リスト7.28 ユーザー登録ページにフラッシュメッセージを追加する。
app/controllers/users_controller.rb
class UsersController < ApplicationController
  .
  .
  .
  def create
    @user = User.new(user_params)
    if @user.save
      flash[:success] = "Welcome to the Sample App!"
      redirect_to @user
    else
      render 'new'
    end
  end

  private

    def user_params
      params.require(:user).permit(:name, :email, :password,
                                   :password_confirmation)
    end
end

7.4.3実際のユーザー登録

ついに、ユーザー登録が完成しました。名前: “Rails Tutorial”、メールアドレス: “example@railstutorial.jp”とでも登録してみましょう。登録結果 (図7.20)にはユーザー登録成功を示すウェルカムメッセージが、successクラスのさわやかな緑色の背景で表示されています。このクラスは5.1.2のBootstrap CSSフレームワークのものです。(もしメールアドレスが既に使用されているというメッセージが表示されたら、7.2でやったようにRakeのdb:resetを実行してデータベースをリセットしてください)。ユーザー表示ページを再度読み込むと、今度はフラッシュメッセージは表示されなくなりました (図7.21)。

signup_flash_bootstrap
図7.20ユーザー登録が成功し、フラッシュメッセージが表示される。(拡大)
signup_flash_reloaded_bootstrap
図7.21ブラウザでページを再読み込みしてフラッシュメッセージが表示されなくなる。(拡大)

今度はデータベースを覗いて、新規ユーザーが確かに登録されていることをダブルチェックしましょう。

$ rails console
>> User.find_by(email: "example@railstutorial.jp")
=> #<User id: 1, name: "Rails Tutorial", email: "example@railstutorial.jp",
created_at: "2013-03-12 05:51:34", updated_at: "2013-03-12 05:51:34",
password_digest: "$2a$10$A58/j7wwh3aAffGkMAO9Q.jjh3jshd.6akh...">

7.4.4 SSLを導入して本番環境をデプロイする

Userモデルとユーザー登録機能の開発が終わったので、今度はこのサンプルアプリケーションを本番 (production) 環境にデプロイしましょう (第3章の設定を行なっていない場合は、今のうちに設定を行なっておいてください)。 デプロイの作業の1つとして、Secure Sockets Layer (SSL)11を本番アプリケーションに追加し、ユーザー登録をセキュアにします。SSLはWebサイト全体で有効にするので、サンプルアプリケーションのユーザー登録 (第8章) もセキュアになり、セッションハイジャックの脆弱性を防止できます (8.2.1)。

デプロイの下準備として、この時点で変更をmasterブランチにマージしておきます。

$ git add .
$ git commit -m "Finish user signup"
$ git checkout master
$ git merge sign-up

デプロイされたアプリケーションが期待どおりに動作するために、SSLが本番環境で動作するための行を1つ追加します。そのためには、config/environments/production.rbファイルを リスト7.29のように変更します。

リスト7.29 本番環境のアプリケーションでSSLを有効にする。
config/environments/production.rb
SampleApp::Application.configure do
  .
  .
  .
  # Force all access to the app over SSL, use Strict-Transport-Security,
  # and use secure cookies.
  config.force_ssl = true
  .
  .
  .
end

本番Webサイトが動作するために、設定ファイルの変更をコミットしてHerokuにプッシュする必要があります。

$ git commit -a -m "Add SSL in production"
$ git push heroku

次に、本番データベースでマイグレーションを実行し、HerokuにUserデータモデルを使用することを通知します。

$ heroku run rake db:migrate

(ここでいくつか警告メッセージが表示されることがありますが、無視しても構いません。)

最後に、リモートサーバーでSSLを設定します。本番WebサイトでSSLを使用するように設定を行うのはかなり面倒で、間違いも起きやすくなっています。また、使用する独自ドメイン向けのSSL証明書も購入しなければなりません。幸い、このサンプルアプリケーションのようにHerokuドメインのまま使用してもよいのであれば、HerokuのSSL証明書を流用することができます。これはHerokuプラットフォームの一部として自動的に利用できる機能です。SSLをexample.comのような独自ドメインで実行したいのであれば、SSL証明書を購入するしかありません。詳細はHerokuのSSL関連ページを参照してください。

これでいよいよ、以下を実行して本番サーバーでユーザー登録フォームが動作するところをブラウザで見ることができます (図7.22)。

$ heroku open

図7.22では、通常のhttp://ではなくhttps://になっていることに注目してください。この余分な ‘s’ はSSLが有効であることを表しています。

これで、好きなだけユーザー登録ページを表示して新規ユーザーを作成できるようになりました。何か問題が生じた場合は、以下を実行して

$ heroku logs

Herokuのログファイルを使用してエラーをデバッグします。

signup_in_production_bootstrap
図7.22本番Webで動作中のユーザー登録ページ。 (拡大)

7.5最後に

ユーザー登録機能の実装は、私たちのサンプルアプリケーションにとって大きなマイルストーンでした。この時点でサンプルアプリケーションはかなり実用的になってきましたが、まだ重要な機能がいくつも残っています。第8章では、認証 (authentication) システムを導入し、ユーザーがサインインとサインアウトを行えるようにします。第9章では、どのユーザーも自分のアカウント情報を更新できるようにし、Webサイトの管理者がユーザーを削除できるようにします。それにより、Usersリソースに表7.1のRESTアクションがすべて実装されるようにします。最後に、認可 (authorization) のためのメソッドをアクションに追加し、Webサイトがセキュリティモデルに従うようにします。

7.6演習

  1. リスト7.30のコードを使用して、7.1.4で定義されたgravatar_forヘルパーにオプションのsizeパラメーターを取ることができる (gravatar_for user, size: 40のようなコードをビューで使用できる) ことを確認してください。
  2. リスト7.23で実装したエラーメッセージをテストするコードを書いてください。リスト7.31のように書き始めるのがお勧めです。
  3. リスト7.32のテストが、createアクションでユーザーを保存した後の動作を正しく捉えていることを確認してください。確認方法は、最初にテストを書いてもよいし、アプリケーションのコードをわざと壊してその後修正しても構いません。リスト7.32では、Capybaraのhave_selectorメソッドを導入しています。これは (特定のCSSクラスに属する) 特定のHTMLタグが存在しているかどうかをテストします。
  4. 既に書いたとおり、リスト7.27のコードは美しいとは言えません。より読みやすくしたリスト7.33のコードに対してテストスイートを実行し、こちらも正常に動作することを確認してください。このコードでは、Railsのcontent_tagヘルパーを使用しています。
リスト7.30 gravatar_forヘルパー用のオプションの:sizeパラメーターを定義する。
app/helpers/users_helper.rb
module UsersHelper

  # Returns the Gravatar (http://gravatar.com/) for the given user.
  def gravatar_for(user, options = { size: 50 })
    gravatar_id = Digest::MD5::hexdigest(user.email.downcase)
    size = options[:size]
    gravatar_url = "https://secure.gravatar.com/avatar/#{gravatar_id}?s=#{size}"
    image_tag(gravatar_url, alt: user.name, class: "gravatar")
  end
end
リスト7.31 お勧めのエラーメッセージテスト。
spec/requests/user_pages_spec.rb
  .
  .
  .
  describe "signup" do

    before { visit signup_path }
    .
    .
    .
    describe "with invalid information" do
      .
      .
      .
      describe "after submission" do
        before { click_button submit }

        it { should have_title('Sign up') }
        it { should have_content('error') }
      end
      .
      .
      .
リスト7.32 createアクションで保存が行われた後の動作をテストする。
spec/requests/user_pages_spec.rb
    .
    .
    .
    describe "with valid information" do
      .
      .
      .
      describe "after saving the user" do
        before { click_button submit }
        let(:user) { User.find_by(email: 'user@example.com') }

        it { should have_title(user.name) }
        it { should have_selector('div.alert.alert-success', text: 'Welcome') }
      end
      .
      .
      .
リスト7.33 サイトのレイアウトにあるflash ERbでcontent_tagを使用する。
app/views/layouts/application.html.erb
<!DOCTYPE html>
<html>
      .
      .
      .
      <% flash.each do |key, value| %>
        <%= content_tag(:div, value, class: "alert alert-#{key}") %>
      <% end %>
      .
      .
      .
</html>
  1. Mockingbirdでは図7.1のプロファイル写真のようなカスタム画像はサポートされていません。ここではAdobe Fireworksを使用して手動で画像を置きました。
  2. このカバの写真はhttps://www.flickr.com/photos/43803060@N00/24308857/から引用しました。
  3. Railsのdebug情報は YAML (一種の再帰的略語であり、“YAML Ain’t Markup Language” の略とされています) 形式で表示されます。YAMLは人間だけでなくコンピュータにとっても読みやすい形式です。
  4. 実は、この3つ以外にもカスタムの環境を作成することができます。詳細については「環境を追加した場合のRailsCast (英語)」を参照してください。
  5. この時点では、ルーティングは動作していますが、対応するページが動作しているとは限りません。たとえば、/users/1/edit がUsersコントローラのeditアクションに正常にルーティングされているとしても、editアクションが存在しなければ、このURLにアクセスしたときにエラーになります。
  6. ヒンズー教では、アバターは人間や動物の形をとって神が顕現したものと考えられています。これを拡大解釈して、アバターという用語は、特にネット界隈で、その人物を表現するもの (かつその人そのものの一部でもある) という意味で使われます。もちろん、映画「アバター」を見た人にはこんな解説は不要でしょう。
  7. アプリケーションでカスタム画像を扱ったりその他のファイルをアップロードする必要があれば、代わりにPaperclip gemかCarrierWave gemをお勧めします。
  8. どうにも気持ちの悪い動作だと思いませんか。私にも何故必要なのか分かりません。
  9. 私はRails APIpluralizeを調べていてたまたま気付きました。
  10. 実際には、これに非常に近いflash.nowを使いますが、本当に必要になるまでは使わないようにしようかと思います。
  11. 技術上は、SSLはTLS (Transport Layer Security) と名称が変わりましたが、未だに “SSL” と呼ばれ続けています。
第7章 ユーザー登録 Rails 4.0 (第2版)