Ruby on Rails チュートリアル
-
第2版 目次
- 第1章ゼロからデプロイまで
- 第2章デモアプリケーション
- 第3章ほぼ静的なページの作成
- 第4章Rails風味のRuby
- 第5章レイアウトを作成する
- 第6章ユーザーのモデルを作成する
- 第7章ユーザー登録
- 第8章サインイン、サインアウト
- 第9章ユーザーの更新・表示・削除
- 第10章ユーザーのマイクロポスト
- 第11章ユーザーをフォローする
- 第12章Rails 4.0へのアップグレード
|
||
第2版 目次
|
Ruby on Rails チュートリアル
プロダクト開発の0→1を学ぼう
下記フォームからメールアドレスを入力していただくと、招待リンクが記載されたメールが届きます。リンクをクリックし、アカウントを有効化した時点から『30分間』解説動画のお試し視聴ができます。
メール内のリンクから視聴を開始できます。
第2版 目次
- 第1章ゼロからデプロイまで
- 第2章デモアプリケーション
- 第3章ほぼ静的なページの作成
- 第4章Rails風味のRuby
- 第5章レイアウトを作成する
- 第6章ユーザーのモデルを作成する
- 第7章ユーザー登録
- 第8章サインイン、サインアウト
- 第9章ユーザーの更新・表示・削除
- 第10章ユーザーのマイクロポスト
- 第11章ユーザーをフォローする
- 第12章Rails 4.0へのアップグレード
第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.2のように2ユーザーのプロファイル写真と基本ユーザーデータ、そしてマイクロポストの一覧を表示することです。(図7.2では、有名なlorem ipsumダミーテキストを使用しています。このテキストの成り立ちには面白いエピソードがあるので機会がありましたらどうぞ。) このページを作成したら、第11章のサンプル・アプリケーションで使用する予定です。
7.1.1デバッグとRails環境
この節で作成するプロファイルは、このアプリケーションにおける初めての真に動的なページになります。ビューそのものは1ページのコードですが、サイトのデータベースから取り出した情報を使用して各プロファイルの表示をカスタマイズします。サンプルアプリケーションに動的なページを追加する準備として、ここでWebサイトのレイアウトにデバッグ情報を追加しましょう (リスト7.1)。これにより、ビルトインのdebug
メソッドとparams
変数を使用して、各プロファイルページにデバッグ用の情報が表示されるようになります (詳細については7.1.2で解説します)。
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章で作成したカスタムスタイルシートをListing 7.2のように追加します。
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のようになります。
図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? %>
は本番アプリケーションやテストで挿入されることはありません。(テスト環境でデバッグ情報が表示されても直接問題になることはありませんが、よいことではありません。デバッグ情報は開発環境以外では使用すべきではありません。)
Railsにはテスト環境 (test)、開発環境 (development)、そして本番環境 (production) の3つの環境がデフォルトで装備されています。Rails consoleのデフォルトの環境はdevelopmentです。
$ rails console Loading development environment >> Rails.env => "development" >> Rails.env.development? => true >> Rails.env.test? => false
上のように、RailsにはRailsというオブジェクトがあり、それには環境の論理値 (boolean) を取るenvという属性があります。たとえば、Rails.env.test?はテスト環境ではtrueを返し、それ以外の環境ではfalseを返します。
テスト環境のデバッグなど、他の環境でconsoleを実行する必要が生じた場合は、環境をパラメータとしてconsoleスクリプトに渡すことができます。
$ rails console test Loading test environment >> Rails.env => "test" >> Rails.env.test? => true
ローカルのRailsサーバーではconsoleのデフォルトの環境としてdevelopmentが使用されますが、以下のように他の環境でconsoleを実行することもできます。
$ rails server --environment production
アプリケーションを本番環境で実行する場合、本番のデータベースが利用できないとアプリケーションを実行できません。そのため、rake db:migrateを本番環境で実行して本番データベースを作成します。
$ bundle exec rake db:migrate RAILS_ENV=production
(注: console、server、migrateの3つのコマンドでは、デフォルト以外の環境を指定する方法がそれぞれ異なっており、混乱を招く可能性があります。このため、3つの場合のすべてを本コラムで説明しました。)
ところで、サンプルアプリケーションをHerokuにデプロイした場合は、herokuコマンドで環境を確認することができます。これを行うには、Herokuの (リモート) コンソールで以下を実行します。
$ heroku run console Ruby console for yourapp.herokuapp.com >> Rails.env => "production" >> Rails.env.production? => true
当然ながら、Herokuは本番サイト用のプラットフォームなので、実行されるアプリケーションはすべて本番環境となります。
7.1.2ユーザーリソース
第6章の最後で、データベースに新しいユーザーを作成しました。6.3.5でこのユーザーのidは1
になっており、今の私たちの目標はこのユーザーの情報を表示するページを作成することです。ここでは、Railsアプリケーションで好まれているRESTアーキテクチャ (コラム 2.2) の習慣に従うことにしましょう。つまり、データを作成、表示、更新、削除可能なリソースとして扱うということです。HTTP標準には、これらに対応する4つの基本操作 (POST、GET、PUT、DELETE)が定義されています (コラム 3.2)。
RESTの原則に従う場合、リソースへの参照はリソース名とユニークIDを使用するのが普通です。ユーザーをリソースとみなす場合、id=1
のユーザーを参照するということは、/users/1というURIに対してGETリクエストを発行するということを意味します。ここでshow
というアクションの種類は、暗黙のリクエストになります。RailsのREST機能が有効になっていると、GETリクエストは自動的にshow
アクションとして扱われます。
2.2.1で説明したとおり、id=1
のユーザーにアクセスするためのページのURIは/users/1となります。ただし、現時点でこのURIを使用してもエラーになります (図 7.4)。
RESTスタイルのURIを有効にするには、routesファイル (config/routes.rb
)に以下の1行を追加します。
resources :users
この行を追加した結果がリスト7.3です。
config/routes.rb
SampleApp::Application.routes.draw do
resources :users
root to: 'static_pages#home'
match '/signup', to: 'users#new'
.
.
.
end
ところで、リスト7.3には以下の行がありません。
get "users/new"
この行はリスト5.32にはありました。実は、resources :users
という行は、動作する /users/1 URIを追加するためだけのものではありません。サンプルアプリケーションにこの行を追加すると、ユーザーのURIを生成するための多数の名前付きルート (5.3.3) に従って、RESTfulなUsersリソースで必要となるすべてのアクションが利用できるようになります5。この行に対応するURI、アクション、名前付きルートは表7.1のようになります (表2.2との違いを比較してみてください)。 次の3つの章に渡って、表7.1の他の項目も利用して、Usersリソースを完全にRESTfulなリソースにするために必要なアクションをすべて作成する予定です。
HTTPリクエスト | URI | アクション | 名前付きルート | 用途 |
---|---|---|---|---|
GET | /users | index | users_path | すべてのユーザーを表示するページ |
GET | /users/1 | show | user_path(user) | 特定のユーザーを表示するページ |
GET | /users/new | new | new_user_path | ユーザーを新規作成するページ (ユーザー登録) |
POST | /users | create | users_path | ユーザーを作成するアクション |
GET | /users/1/edit | edit | edit_user_path(user) | id=1 のユーザーを編集するページ |
PUT | /users/1 | update | user_path(user) | ユーザーを更新するアクション |
DELETE | /users/1 | destroy | user_path(user) | ユーザーを削除するアクション |
リスト7.3のコードを使用することで、ルーティングが有効になります。ただし、ルーティング先のページはまだありません (図7.5)。この問題を解決するために、7.1.4で最小限のプロファイルページを作成する予定です。
ユーザーを表示するために、標準的なRailsの場所を使用することにします。app/views/users/show.html.erb
です。リスト5.28でジェネレータを使用して作成したnew.html.erb
ビューと異なり、このshow.html.erb
ファイルは自動的には作成されないので、手動で作成します。このファイルを作成後、リスト7.4の内容を貼り付けてください。
app/views/users/show.html.erb
<%= @user.name %>, <%= @user.email %>
このビューでは埋め込みRubyを使用してユーザー名とメールアドレスを表示しています。インスタンス変数@user
があることを前提としています。もちろん、ユーザー表示ページの最終的な状態はこれとは大きく異なりますし、このメールアドレスがこのまま一般に公開されるようなこともありません。
ユーザー表示ビューが正常に動作するためには、Usersコントローラ内のshow
アクションに対応する@user
変数を定義する必要があります。 ご想像のとおり、ここではUserモデルのfind
メソッド (6.1.4) を使用してデータベースからユーザーを取り出します。リスト7.5のように書き換えてください。
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)。
7.1.3ファクトリーを使用してユーザー表示ページをテストする
最小限のプロファイルが動作するようになりましたので、図7.1のモックアップバージョンの作成を始めましょう。静的なページの作成 (第3章) やUserモデル (第6章) のときと同様に、テスト駆動開発を行います。
5.4.2では、Usersリソースに関連付けられたページに対して行った結合テストを思い出してください。ユーザー登録ページでは、リスト5.31のように最初にsignup_path
をテストし、次にh1
タグとtitle
タグが正しいことをチェックしました。これを再現したものがリスト7.6です (5.3.4のfull_title
ヘルパーについては、フルタイトルが既にテスト済みであることから省略しました)。
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_selector('h1', text: 'Sign up') }
it { should have_selector('title', text: 'Sign up') }
end
end
ユーザー表示ページをテストするために、show
アクションのコード (リスト7.5) で検索を行うUserモデルオブジェクトが必要となります。
describe "profile page" do
# Code to make a user variable
before { visit user_path(user) }
it { should have_selector('h1', text: user.name) }
it { should have_selector('title', text: user.name) }
end
これよりコメントの部分に適切なコードを追加します。ここではuser_path
という名前付きルート (表7.1) を使用して、指定されたユーザーのページを表示するためのパスを生成します。これにより、h1
タグとtitle
タグの両方にユーザーの名前が含まれているかどうかをテストできます。
必要な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
グループに追加します)。
Gemfile
にFactory Girlを追加する。 source 'https://rubygems.org'
.
.
.
group :test do
.
.
.
gem 'factory_girl_rails', '4.1.0'
end
.
.
.
end
インストール方法は、いつものように以下を実行します。
$ bundle install
Factory Girlのファクトリーはすべてspec/factories.rb
ファイルに置きます。ここにあるファクトリーはRSpecによって自動的に読み込まれます。Userファクトリが見えるようにするためのコードはリスト7.8です。
spec/factories.rb
FactoryGirl.define do
factory :user do
name "Michael Hartl"
email "michael@example.com"
password "foobar"
password_confirmation "foobar"
end
end
シンボル:user
がfactory
コマンドに渡されると、Factory Girlはそれに続く定義がUserモデルオブジェクトを対象としていることを認識します。
リスト7.8の定義をしたことにより、let
コマンド (コラム 6.3) とFactory GirlのFactoryGirl
メソッドを使ってUserのファクトリーを作成することができるようになります。
let(:user) { FactoryGirl.create(:user) }
最終的なコードはリスト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_selector('h1', text: user.name) }
it { should have_selector('title', text: user.name) }
end
.
.
.
end
この時点で以下を実行すると、テストスイートが赤色 (失敗) になるはずです。
$ bundle exec rspec spec/
このコードは、リスト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では、セキュアハッシュを生成する際の計算の負荷をコストファクター (cost factor) として指定できます。コストファクターのデフォルト値は速度よりセキュアであることを重視しています。これは本番環境では最適ですが、テスト環境では不利です。私たちはテストを高速で行いたいのであり、ユーザーのパスワードハッシュのセキュリティについてはテスト中は気にしたくありません。解決方法は、テスト設定ファイルconfig/environments/test.rb
にリスト7.11のように数行追加することです。これによりコストファクターが再定義され、デフォルトのセキュア重視から速度重視の最小値に変わります。 かなり小規模なテストであっても、この修正によって速度は相当向上します。test.rb
にはいつもリスト7.11の数行を追加しておくことをぜひともお勧めします。
config/environments/test.rb
SampleApp::Application.configure do
.
.
.
# Speed up tests by lowering BCrypt's cost function.
require 'bcrypt'
silence_warnings do
BCrypt::Engine::DEFAULT_COST = BCrypt::Engine::MIN_COST
end
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の画像を利用できるようにします。
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のURIは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に示しました。
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というドメイン名は、例として使用するために特別に予約されています)。
アプリケーションでカスタムGravatarを利用できるようにするために、update_attributes
(6.1.5) を使用してデータベース上のユーザー情報を更新します。
$ rails console
>> user = User.first
>> user.update_attributes(name: "Example User",
?> email: "example@railstutorial.jp",
?> password: "foobar",
?> password_confirmation: "foobar")
=> true
ここではユーザーのメールアドレスに example@railstutorial.jp
を使用しました (図7.8)。このメールアドレスはRailsチュートリアルのロゴでも使用されています。
図7.1のモックアップに近づけるために、ユーザーのサイドバーの最初のバージョンを作りましょう。ここではaside
タグを使用して実装します。このタグはサイドバーなどの補完コンテンツの表示に使用されますが、単独で表示することもできます。row
クラスとspan4
クラスも追加しておきます。これらのクラスはBootstrapの一部です。ユーザー表示ページを変更した結果をリスト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のルールがネスティング (入れ子) されていますが、これが有効になるのはアセットパイプラインでSassエンジンが使用されている場合に限られます) 。ページの変更の結果を図7.9に示します。
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;
}
7.2ユーザー登録フォーム
ここまででユーザープロファイルページがひとまず動作するようになりましたので、今度はサインアップフォームを作成しましょう。図5.9 (図7.10にも再録) に示したとおり、サインアップページはまだ空白のままなので、このままではサインアップできません。この節の目標は、このみっともないページを改造して図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.count
は0
になっています。
無効なデータを送信した場合、ユーザーのカウントが変わらないことを期待しています。有効なデータを送信した場合には、ユーザーのカウントが1つ増えることを期待しています。このような動作は、RSpecでexpect
メソッドをto
メソッドまたは not_to
メソッドと組み合わせて実現できます。確認しやすいので、まずは無効な場合についてやってみましょう。ユーザー登録ページのパスをブラウザで開いてボタンをクリックしたら、ユーザーアカウントが変更されないという動作です。
visit signup_path
expect { click_button "Create my account" }.not_to change(User, :count)
ここで、expect
がclick_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
initial.should == final
ボタンクリックをブロックで囲むことによって以下のように1行で表しています。
expect { click_button "Create my account" }.not_to change(User, :count)
こうすることで英語に近い自然な表記が可能になり、さらにコンパクトになります。
データが有効な場合のテストも大きな違いはありませんが、今度はカウントが更新されないのではなく、カウントが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
変数を定義しています。
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 ... %>という書式でしたが、Rails 3では<%= form_for ... %>という書式です)。
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のユーザー登録ページの構造 (h1
やtitle
など) に対するテストは失敗します。
$ 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
とします。
@user
変数をnew
アクションに追加する。app/controllers/users_controller.rb
class UsersController < ApplicationController
.
.
.
def new
@user = User.new
end
end
@user
変数が上記のように定義されれば、以下のユーザー登録のテストはパスするはずです。
$ bundle exec rspec spec/requests/user_pages_spec.rb -e "signup page"
この時点で、フォーム にリスト7.19のようにスタイルが与えられていれば、図7.12のように表示されます。box_sizing
ミックスインをリスト7.2から再利用していることに注目してください。
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;
}
7.2.3フォームHTML
図7.12に示したように、ユーザー登録は正常に表示されるようになりました。これは、リスト7.17のform_for
コードが正しいHTMLを生成しているということです。生成されたフォームのHTMLソースを見てみると (Firebugを使うか、ブラウザの [ソースを表示] を使用)、ソースのマークアップはリスト7.20のようになっているはずです。この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]" size="30" type="text" />
<label for="user_email">Email</label>
<input id="user_email" name="user[email]" size="30" type="text" />
<label for="user_password">Password</label>
<input id="user_password" name="user[password]" size="30"
type="password" />
<label for="user_password_confirmation">Confirmation</label>
<input id="user_password_confirmation"
name="user[password_confirmation]" size="30" 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]" size="30" type="text" />
さらに、以下の埋め込みRubyは
<%= f.label :password %>
<%= f.password_field :password %>
以下のHTMLを生成していることがわかります。
<label for="user_password">Password</label><br />
<input id="user_password" name="user[password]" size="30" type="password" />
次の図7.13に示すように、テキストフィールド (type="text"
) では内容をそのまま表示していますが、パスワードフィールド (type="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.2)。
<form action="/users" class="new_user" id="new_user" method="post">
ここでclass
とid
属性はほぼ無関係です。ここで重要な属性はaction="/users"
とmethod="post"
です。これらの2つの属性は、HTTP POSTリクエストに対する指示を構成します。これらの属性の効用については次の2つの節で説明します。
7.3ユーザー登録失敗
図7.12ではフォームのHTMLがどうなっているかを簡単に説明しました (リスト7.20参照) が、この方法はサインアップの失敗のときに最も理解の助けになります。この節では、無効なデータ送信を受け付けるサインアップフォームを作成し、サインアップフォームを更新してエラーの一覧を表示します。このモックアップを図7.14に示します。
7.3.1正しいフォーム
最初に、ユーザー登録フォームを送信したときに発生するエラーを解消します。これはブラウザでも確認ですが、ユーザー登録フォームで無効な情報を入力するテストを実施することでも確認できます。
$ bundle exec rspec spec/requests/user_pages_spec.rb \
-e "signup with invalid information"
7.1.2で、resources :users
をroutes.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 URIに送信します。
リスト7.16にある無効な情報を使用するテストを使用して、リスト7.21のコードがテストにパスするようにすることができます。このリストでは、5.1.3の「パーシャル」のところでも使ったrender
メソッドを再度使いまわしています。render
はコントローラアクションの中でも正常に動作します。ここで、以前に説明したif
-else
分岐構造を思い出してください。この文を使用して、保存が成功したかどうかに応じて@user.save
の値がtrue
またはfalse
(6.1.3) になるときに、それぞれ成功時の処理と失敗時の処理を場合分けすることができます。
create
アクション。app/controllers/users_controller.rb
class UsersController < ApplicationController
.
.
.
def create
@user = User.new(params[:user])
if @user.save
# Handle a successful save.
else
render 'new'
end
end
end
リスト7.21のコードの動作を理解するもっともよい方法は、実際に無効なユーザー登録データを送信 (submit)してみることです。結果を図7.15に、完全なデバッグ情報を図7.16に示しました。
Railsが送信を扱う方法をより深く理解するために、デバッグ情報のうちparams
ハッシュの部分を詳しく見てみましょう (図7.16)。
---
user:
name: Foo Bar
password_confirmation: foo
password: bar
email: foo@invalid
commit: Create my account
action: create
controller: users
7.1.2で説明したとおり、params
ハッシュには各リクエストの情報が含まれています。/users/1のようなURIの場合、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]" size="30" type="text" />
"user[email]"
という名前は、user
ハッシュのemail
属性を正確に指します。
ハッシュのキーはデバッグ情報では文字列として表現されていますが、Railsはこれらを文字列ではなく、params[:user]
がuser属性のハッシュになるような「シンボル」(Rubyの機能の1つで、一意性が保証された特別な文字列) として扱います。実際、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")
もちろん、この変数が継承されたということはユーザー登録が成功したということでもあります。7.4でも説明しますが、従って、@user
が正しく定義されてしまえば、@user.save
を行うだけで登録が完了します (ただし、今の段階ではユーザー登録が失敗した場合にも同じ動作になってしまいますが)。図7.15のフィールドは、送信に失敗したときのデータが自動的に入力済みの状態になっています。これは、form_for
が@user
オブジェクトの属性を自動的にフィールドに入力しているからです。たとえば、@user.name
の値が"Foo"
であれば
<%= form_for(@user) do |f| %>
<%= f.label :name %>
<%= f.text_field :name %>
.
.
.
上のコードによって以下のHTMLが生成されます。
<form action="/users" class="new_user" id="new_user" method="post">
<label for="user_name">Name</label><br />
<input id="user_name" name="user[name]" size="30" type="text" value="Foo"/>
.
.
.
ここでinput
タグのvalue
属性は "Foo"
になっています。これがテキストフィールドに表示されているのです。
ご想像のとおり、この段階ではフォームを送信してもエラーが生じなくなりました。以下の無効なデータ送信テストもパスするようになっているはずです。
$ bundle exec rspec spec/requests/user_pages_spec.rb \
-e "signup with invalid information"
7.3.2ユーザー登録のエラーメッセージ
必須というわけではありませんが、ユーザー登録に失敗した場合は、ユーザー登録が行われなかったということをユーザーに伝えるエラーメッセージを表示する方がよいでしょう。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.22に示します。(このエラーメッセージのテストを行うのはよいことですが、演習の課題としてあえて残しておきます。7.6を参照してください。) ここで使用しているエラーメッセージのパーシャルはあくまで試作品である点に注意してください。最終版は10.3.2を参照してください。
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’
というパーシャルを出力 (レンダリング)
している点に注目してください。これはRails全般の慣習で、パーシャルは複数のコントローラにわたるビューに対し、専用のshared/
ディレクトリを使用するようにしています (これは9.1.1で実現します)。 つまり、app/views/shared
ディレクトリと_error_messages.html.erb
パーシャルファイルの両方を作成する必要があるということです。パーシャル自身の内容をリスト7.23に示します。
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
を返します (なお、これらのcount
、empty?
、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.23には、エラーメッセージにスタイルを与えるためのCSS id error_explanation
も含まれていることに注目してください (5.1.2でCSSがスタイルidに「#
」記号を使用していることを思い出してください)。さらにRailsは、エラーページにある、div
で囲まれたエラーCSSクラスfield_with_errors
を適用しています。これらのラベルによって、リスト7.24のようにエラーメッセージをSCSSで整形することができます。ここでは、Sassの@extend
関数を使用してBootstrapのcontrol-group
とerror
の2つのクラスの機能をインクルードしています。その結果、送信失敗時のエラーメッセージは図7.17のように赤の背景で囲まれるようになります。これらのメッセージはモデルの検証時に生成されるので、メールアドレスのスタイルやパスワードの最小文字列などを変更すると、メッセージも自動的に変更されます。
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;
}
この節で達成した成果を確認するために、リスト7.16のユーザー登録失敗のテストを再度実行し、ユーザー登録ページを表示して入力フィールドが空白のまま [Create my account] をクリックするという動作をテストしましょう。 テストの結果をFigure 7.18に示します。今度は期待どおりテストにパスするはずです。
$ bundle exec rspec spec/requests/user_pages_spec.rb \
> -e "signup with invalid information"
残念ながら、図7.18のエラーメッセージには若干問題が残っています。このエラーメッセージは “Password digest can’t be blank” となっていますが、本来であれば “Password can’t be blank” とすべきでしょう。これは、6.3.4で簡単に説明したhas_secure_password
の中に、パスワードダイジェストの存在を検証するコードが隠れているためです。この問題の修正は、演習問題とさせてください (7.6)。
7.4ユーザー登録成功
無効なフォームの送信を扱えるようになったので、いよいよ新規ユーザーを実際にデータベースに保存できるようにし (もちろんフォームが有効な場合に)、ユーザー登録フォームを完成させましょう。まずは、ユーザーを保存できるようにします。保存に成功すると、ユーザー情報は自動的にデータベースに登録されます。次にブラウザの表示をリダイレクトして、登録されたユーザーのプロファイルを表示します。ついでにウェルカムメッセージも表示しましょう。モックアップをFigure 7.19に示します。保存に失敗した場合は、単に7.3で開発したとおりの動作が実行されます。
7.4.1登録フォームの完成
ユーザー登録フォームを完成させるために、リスト7.21のコメントアウトされた部分にコードを書き、適切に動作するようにしましょう。現時点では、以下の有効な送信テストは失敗するはずです。
$ bundle exec rspec spec/requests/user_pages_spec.rb \
> -e "signup with valid information"
Railsのデフォルトのアクションは対応するビューを表示するようになっていますが、create
アクションに対応するビューのテンプレートがない (あるはずがありません) ため、テストに失敗します。ここでは、登録後に別のページを表示するようにし、そのページが新規作成されたユーザープロファイルであることがわかるようにします。正しいページが出力されているかどうかのテストは、演習に回すことにします (7.6)。アプリケーションのコードをリスト7.25に示します。
create
アクション。app/controllers/users_controller.rb
class UsersController < ApplicationController
.
.
.
def create
@user = User.new(params[:user])
if @user.save
redirect_to @user
else
render 'new'
end
end
end
リダイレクトではuser_url
を省略し、単にredirect_to @user
と書けばユーザー表示ページに移動します。
リスト7.25のコードを使用することで、ユーザー登録ページは完成し、動作するようになります。以下のテストを実行することもできます。
$ bundle exec rspec spec/
7.4.2flash
いよいよブラウザで正しいユーザー情報を登録できるようになりましたが、その前にWebアプリケーションに常識的に備わっている機能を追加してみましょう。登録完了後に表示されるページにメッセージを表示し (この場合は新規ユーザーへのウェルカムメッセージ)、2度目以降にはそのページにメッセージを表示しないようにするというものです。Railsでは、こういう場合にflashという特殊な変数を使用できます。この変数はちょうどフラッシュメモリーのようにデータを一時的にのみ保存することからこう呼ばれています。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.26のようにアプリケーションのレイアウトに含めれば、flash変数の内容をWebサイト全体にわたって表示できるようにすることもできます (このコードは、HTMLとERbがミックスされていて美しくありません。コードの改良は7.6で解説します)。
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.26のコードは、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.27のように、create
アクションでflash[:success]
にウェルカムメッセージを割り当てれば 、テストにパスするようになります。
app/controllers/users_controller.rb
class UsersController < ApplicationController
.
.
.
def create
@user = User.new(params[:user])
if @user.save
flash[:success] = "Welcome to the Sample App!"
redirect_to @user
else
render 'new'
end
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)。
今度はデータベースを覗いて、新規ユーザーが確かに登録されていることをダブルチェックしましょう。
$ rails console
>> User.find_by_email("example@railstutorial.jp")
=> #<User id: 1, name: "Rails Tutorial", email: "example@railstutorial.jp",
created_at: "2011-12-13 05:51:34", updated_at: "2011-12-13 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.28のように変更します。
config/environments/production.rb
SampleApp::Application.configure do
.
.
.
# アプリケーションへのすべてのアクセスをSSL経由にし、
# Strict-Transport-Securityを使用し、
# 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
Figure 7.22では、通常のhttp://ではなくhttps://になっていることに注目してください。この余分な ‘s’ はSSLが有効であることを表しています。
今や好きなだけユーザー登録ページを表示して新規ユーザーを作成できます。何か問題が生じた場合は、以下を実行して
$ heroku logs
Herokuのログファイルを使用してエラーをデバッグします。
7.5最後に
ユーザー登録機能の実装は、私たちのサンプルアプリケーションにとって大きなマイルストーンでした。この時点でサンプルアプリケーションはかなり実用的になってきましたが、まだ重要な機能がいくつも残っています。第8章では、認証 (authentication) システムを導入し、ユーザーがサインインとサインアウトを行えるようにします。第9章では、どのユーザーも自分のアカウント情報を更新できるようにし、Webサイトの管理者がユーザーを削除できるようにします。それにより、Usersリソースに表7.1のRESTアクションがすべて実装されるようにします。最後に、認可 (authorization) のためのメソッドをアクションに追加し、Webサイトがセキュリティモデルに従うようにします。
7.6演習
- リスト7.29のコードで、7.1.4で定義された
gravatar_for
ヘルパーにオプションのsize
パラメーターを取ることができる (gravatar_for user, size: 40
のようなコードをビューで使用できる) ことを確認してください。 - リスト7.22で実装したエラーメッセージをテストするコードを書いてください。リスト7.31のように書き始めるのがお勧めです。
- リスト7.30のコードを使用して、パスワードがない場合のエラーメッセージを別のメッセージに置き換えてください。現在のメッセージは “Password digest can’t be blank”ですが、よりわかりやすい “Password can’t be blank” に置き換えます。(ここではRailsの国際化 (internationalization) サポートを使用して実現していますが、これはどちらかというと荒っぽい (hacky) 方法です。YAMLの書式にタブを使用しないよう、代わりにスペースを使用するよう十分注意してください ("YAMLフォーマットではタブは使用できない")。) エラーメッセージが重複して表示されないように、Userモデルでパスワードの
presence: true
検証を削除しておく必要もあります。 - リスト7.32のテストが、
create
アクションでユーザーを保存した後の動作を正しく捉えていることを確認してください。確認方法は、最初にテストを書いてもよいし、アプリケーションのコードをわざと壊してその後修正しても構いません。 - 既に書いたとおり、リスト7.26のコードは美しいとは言えません。より読みやすくしたリスト7.33のコードに対してテストスイートを実行し、こちらも正常に動作することを確認してください。このコードでは、Railsの
content_tag
ヘルパーを使用しています。
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
config/locales/en.yml
en:
activerecord:
attributes:
user:
password_digest: "Password"
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_selector('title', text: 'Sign up') }
it { should have_content('error') }
end
.
.
.
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_selector('title', text: user.name) }
it { should have_selector('div.alert.alert-success', text: 'Welcome') }
end
.
.
.
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>
- Mockingbirdでは図7.1のプロファイル写真のようなカスタム画像はサポートされていません。ここではAdobe Fireworksを使用して手動で画像を置きました。↑
- このカバの写真はhttps://www.flickr.com/photos/43803060@N00/24308857/から引用しました。↑
- Railsの
デバッグ
情報は YAML (一種の再帰的略語であり、“YAML Ain’t Markup Language” の略とされています) 形式で表示されます。YAMLは人間だけでなくコンピュータにとっても読みやすい形式です。↑ - 実は、この3つ以外にもカスタムの環境を作成することができます。詳細については環境を追加した場合のRailsCastを参照してください。↑
- この時点では、ルーティングは動作していますが、対応するページが動作しているとは限りません。たとえば、/users/1/edit がUsersコントローラの
edit
アクションに正常にルーティングされているとしても、edit
アクションが存在しなければ、このURIにアクセスしたときにエラーになります。↑ - ヒンズー教では、アバターは人間や動物の形をとって神が顕現したものと考えられています。これを拡大解釈して、アバターという用語は、特にネット界隈で、その人物を表現するもの (かつその人そのものの一部でもある) という意味で使われます。もちろん、映画「アバター」を見た人にはこんな解説は不要でしょう。↑
- アプリケーションでカスタム画像を扱ったりその他のファイルをアップロードする必要があれば、代わりにPaperclip gemをお勧めします。↑
- どうにも気持ちの悪い動作だと思いませんか。私にも何故必要なのか分かりません。↑
- 私はRails APIで
pluralize
を調べていてたまたま気付きました。↑ - 実際には、これに非常に近い
flash.now
を使いますが、本当に必要になるまでは使わないようにしようかと思います。↑ - 技術上は、SSLはTLS (Transport Layer Security) と名称が変わりましたが、未だに “SSL” と呼ばれ続けています。↑
Railsチュートリアルは YassLab 社によって運営されています。
コンテンツを継続的に提供するため、書籍・動画・質問対応サービスなどもご検討していただけると嬉しいです。
研修支援や教材連携にも対応しています。note マガジンや YouTube チャンネルも始めたので、よければぜひ遊びに来てください!