Ruby on Rails チュートリアル
-
第3版 目次
- 第1章 ゼロからデプロイまで
- 第2章 Toyアプリケーション
- 第3章 ほぼ静的なページの作成
- 第4章 Rails風味のRuby
- 第5章 レイアウトを作成する
- 第6章 ユーザーのモデルを作成する
- 第7章 ユーザー登録
- 第8章 ログイン、ログアウト
- 第9章 ユーザーの更新・表示・削除
- 第10章 アカウント有効化とパスワード再設定
- 第11章 ユーザーのマイクロポスト
- 第12章 ユーザーをフォローする
|
||
第3版 目次
|
||
最新版を読む |
Ruby on Rails チュートリアル
プロダクト開発の0→1を学ぼう
下記フォームからメールアドレスを入力していただくと、招待リンクが記載されたメールが届きます。リンクをクリックし、アカウントを有効化した時点から『30分間』解説動画のお試し視聴ができます。
メール内のリンクから視聴を開始できます。
第3版 目次
- 第1章 ゼロからデプロイまで
- 第2章 Toyアプリケーション
- 第3章 ほぼ静的なページの作成
- 第4章 Rails風味のRuby
- 第5章 レイアウトを作成する
- 第6章 ユーザーのモデルを作成する
- 第7章 ユーザー登録
- 第8章 ログイン、ログアウト
- 第9章 ユーザーの更新・表示・削除
- 第10章 アカウント有効化とパスワード再設定
- 第11章 ユーザーのマイクロポスト
- 第12章 ユーザーをフォローする
第7章 ユーザー登録
Userモデルができあがったので、いよいよユーザー登録機能を追加しましょう。7.2ではHTMLフォームを使用して登録情報をWebアプリケーションに送信します。続いて7.4ではユーザーを新規作成して情報をデータベースに保存します。ユーザー登録手続きの最後には、作成されたユーザーの新しいプロファイルを表示できるようにするために、ユーザーを表示するためのページを作成し、ユーザー用のRESTアーキテクチャを実装する第一歩を踏み出します (2.2.2)。それに伴い、5.3.4で実装した簡明かつ表現豊かな統合テストに対して、 いくつかのテストを追加していきます。
本章では、第6章で作成したUserモデルのバリデーションを信頼し、有効なメールアドレスを持っている (可能性のある) 新規ユーザーを増やしていきます。第10章では、 メールアドレスが本当に有効であることを確かめるために、アカウントを有効化する機能をサインアップの手順に追加します。
7.1 ユーザーを表示する
この節では、まずユーザーの名前とプロファイル写真を表示するためのページを作成します。モックアップを図7.1に示しました1。ユーザープロファイルページの最終的な目標は、図7.2のように2ユーザーのプロファイル写真と基本ユーザーデータ、そしてマイクロポストの一覧を表示することです。(図7.2では、有名なlorem ipsumダミーテキストを使用しています。このテキストの成り立ちには面白いエピソードがあるので機会がありましたらどうぞ。)このページを作成したら、第12章のサンプル・アプリケーションで使用する予定です。
バージョン管理を使用している場合は、いつもと同じようにトピックブランチを作成します。
$ git checkout master
$ git checkout -b sign-up
7.1.1 デバッグとRails環境
この節で作成するプロファイルは、このアプリケーションにおける初めての真に動的なページになります。ビューそのものは1ページのコードですが、アプリケーションのデータベースから取り出した情報を使用して各プロファイルの表示をカスタマイズします。サンプルアプリケーションに動的なページを追加する準備として、ここでWebサイトのレイアウトにデバッグ情報を追加しましょう (リスト7.1)。これにより、ビルトインのdebug
メソッドとparams
変数を使用して、各プロファイルページにデバッグ用の情報が表示されるようになります (詳細については7.1.2で解説します)。
<!DOCTYPE html>
<html>
.
.
.
<body>
<%= render 'layouts/header' %>
<div class="container">
<%= yield %>
<%= render 'layouts/footer' %>
<%= debug(params) if Rails.env.development? %>
</div>
</body>
</html>
本番環境に展開したアプリケーションではデバッグ情報を表示したくないので、リスト7.1には以下を記述してあります。
if Rails.env.development?
こうしておくと、デバッグ情報はRailsの3つのデフォルト環境のうち、開発環境でしか表示されなくなります。(コラム7.1)3。特に、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コマンドと同様に、rails serverもデフォルトではdevelopment環境が使用されますが、以下のように明示的に他の環境を指定してサーバーを実行することもできます。
$ rails server --environment production
アプリケーションを本番環境で実行する場合、本番のデータベースが利用できないとアプリケーションを実行できません。そのため、rake db:migrateを本番環境で実行して本番データベースを作成します。
$ bundle exec rake db:migrate RAILS_ENV=production
(注: console、server、migrateの3つのコマンドでは、デフォルト以外の環境を指定する方法がそれぞれ異なっており、混乱を招く可能性があります。このため、3つの場合のすべてを本コラムで説明しました。)
ところで、サンプルアプリケーションを既にHeroku上にデプロイしている場合は、heroku run consoleというコマンドを打つことで、本番環境を確認することができます。
$ heroku run console >> Rails.env => "production" >> Rails.env.production? => true
当然ながら、Herokuは本番サイト用のプラットフォームなので、実行されるアプリケーションはすべて本番環境となります。
デバッグ出力をきれいに整形するために、第5章で作成したカスタムスタイルシートをリスト7.2のように追加します。
@import "bootstrap-sprockets";
@import "bootstrap";
/* mixins, variables, etc. */
$gray-medium-light: #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.1でも使用します。今回の場合、デバッグ出力は図7.3のようになります。
図7.3のデバッグ出力には、描画されるページの状態を把握するのに役立つ情報が含まれます。
---
controller: static_pages
action: home
これはparams
の内容で、YAML4というものです。YAMLは基本的にハッシュであり、コントローラとページのアクションを一意に指定します。7.1.2には別の例もあります。
7.1.2 Usersリソース
ユーザープロファイルページを作成するには、その前にデータベースにユーザーが登録されている必要があります。これはいわゆる「卵が先か鶏が先か」問題です。このWebサイトでは、登録ページがない状態でどうやってユーザーを登録しておけばよいでしょうか。幸い、この問題は既に解決されています。6.3.4でRailsコンソールを使用してユーザーレコードを登録してありました。したがって、データベースの中に一人のユーザーがいるはずです。
$ rails console
>> User.count
=> 1
>> User.first
=> #<User id: 1, name: "Michael Hartl", email: "mhartl@example.com",
created_at: "2014-08-29 02:58:28", updated_at: "2014-08-29 02:58:28",
password_digest: "$2a$10$YmQTuuDNOszvu5yi7auOC.F4G//FGhyQSWCpghqRWQW..."=> 6
(もしまだデータベース上に一人もユーザーがいない場合は、6.3.4に戻ってユーザーを追加してください。) 先ほど、コンソールの出力結果からユーザーのIDが 1
であることを確認しました。次の目標は、このようなユーザー情報をWebアプリケーション上に表示することです。ここでは、Railsアプリケーションで好まれているRESTアーキテクチャ (コラム 2.2) の習慣に従うことにしましょう。つまり、データを作成、表示、更新、削除可能なリソースとして扱うということです。HTTP標準には、これらに対応する4つの基本操作 (POST、GET、PATCH、DELETE) が定義されています (コラム3.2)。
RESTの原則に従う場合、リソースへの参照はリソース名とユニークIDを使用するのが普通です。ユーザーをリソースとみなす場合、id=1
のユーザーを参照するということは、/users/1というURLに対してGETリクエストを発行するということを意味します。ここでshow
というアクションの種類は、暗黙のリクエストになります。RailsのREST機能が有効になっていると、GETリクエストは自動的にshow
アクションとして扱われます。
2.2.1で説明したとおり、id=1
のユーザーにアクセスするためのページのURLは/users/1となります。ただし、現時点でこのURLを使用してもエラーになります (図7.4)。
/users/1 のURLを有効にするために、routesファイル (config/routes.rb
)に以下の1行を追加します。
resources :users
変更の結果を7.3に示します。
Rails.application.routes.draw do
root 'static_pages#home'
get 'help' => 'static_pages#help'
get 'about' => 'static_pages#about'
get 'contact' => 'static_pages#contact'
get 'signup' => 'users#new'
resources :users
end
今の短期的な目的はWebページ上にユーザーを表示することではありますが、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 | /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 のユーザーを編集するページ
|
PATCH | /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の内容を貼り付けてください。
<%= @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に置き換わります。つまり、この箇所は6.1.4で学んだfind
メソッドの User.find(1)
と同じになります。(技術的な補足: params[:id]
は文字列型の "1"
ですが、find
メソッドでは自動的に整数型に変換されます)。
ユーザーのビューとアクションが定義されたので、URL /users/1 は完全に動作するようになりました (図7.6)。(もしbcrypt gemを追加してからまだ一度もRailsサーバを再起動させていない場合は、ここで再起動してください。) 図7.6のデバッグ情報でparams[:id]
の値を確認できることにも注目してください。
---
action: show
controller: users
id: '1'
以下のコードを使用して
User.find(params[:id])
id=1のユーザーを検索できたのは以上の仕組みによるものです (リスト7.5)。
7.1.3 デバッガー
7.1.2で、アプリケーションの振る舞いを理解するためにデバッグ情報
が役に立つことを学びました。Rails 4.2からは、byebug gemを使ってもっと直接的にデバッグできるようになりました (リスト3.2)。どういう風にデバッグできるようになったのか、デバッガー
をアプリケーションに差し込んで実際に確かめてみましょう (リスト7.6)。
class UsersController < ApplicationController
def show
@user = User.find(params[:id])
debugger
end
def new
end
end
上のようにdebuggerを差し込んだ後に /users/1 にアクセスしてみると、Railsサーバがbyebug
のプロンプトを表示するようになります。
(byebug)
ここではRailsコンソールのようにコマンドを呼び出すことができて、アプリケーションの今の状態を確認することができます。
(byebug) @user.name
"Example User"
(byebug) @user.email
"example@railstutorial.org"
(byebug) params[:id]
"1"
Ctrl-Dを押すとプロンプトから抜け出すことができます。また、デバッグが終わったらshow
アクション内のdebugger
の行を削除してしまいましょう (リスト7.7)。
class UsersController < ApplicationController
def show
@user = User.find(params[:id])
end
def new
end
end
今後Railsアプリケーションの中でよく分からない挙動があったら、上のようにdebugger
を差し込んで調べてみましょう。トラブルが起こっていそうなコードの近くに差し込むのがコツです。byebugを使ってシステムの状態を調査することは、アプリケーション内のエラーを追跡したりデバッグするときに非常に強力なツールになります。
7.1.4 Gravatar画像とサイドバー
前節で基本的なユーザーページの定義は終わりましたので、今度は各ユーザーのプロファイル写真のあたりをもう少し肉付けし、サイドバーも作り始めましょう。ここでは世界共通のアバターとして認識されている「Gravatar」をユーザープロファイルに導入してみましょう6 (訳注: Gravatarアカウントを作成する必要はありません)。Gravatarは無料のサービスで、プロファイル写真をアップロードして、指定したメールアドレスと関連付けることができます。Gravatarは、プロファイル写真をアップロードするときの面倒な作業や、写真が欠けたりなどのトラブル、置き場所の悩みを解決します。ユーザーのメールアドレスを組み込んだGravatar専用の画像パスを構成するだけで、対応するGravatarの画像が自動的に表示されます (カスタム画像を扱う方法については11.4で扱います)。
ここでは、リスト7.8のようにgravatar_for
ヘルパーメソッドを使用してGravatarの画像を利用できるようにします。
<% provide(:title, @user.name) %>
<h1>
<%= gravatar_for @user %>
<%= @user.name %>
</h1>
デフォルトでは、ヘルパーファイルで定義されているメソッドは自動的にすべてのビューで利用できます。ここでは、利便性を考えて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
の引数を小文字に変換しています。(本チュートリアルでは、リスト6.31のコールバック処理で小文字変換されたメールアドレスを利用しているため、ここで小文字変換を入れなくても結果は同じです。ただし、将来gravatar_for
メソッドが別の場所から呼びだされる可能性を考えると、ここで小文字変換を入れることには意義があります。) gravatar_for
ヘルパーを組み込んだ結果をリスト7.9に示しました。
gravatar_for
ヘルパーメソッドを定義する app/helpers/users_helper.rb
module UsersHelper
# 引数で与えられたユーザーのGravatar画像を返す
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.9のコードは、Gravatarの画像タグにgravatar
クラスとユーザー名のaltテキストを追加したものを返します (altテキストを追加しておくと、視覚障害のあるユーザーがスクリーンリーダーを使用するときにも役に立ちます)。
プロフィールページは図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.org",
?> password: "foobar",
?> password_confirmation: "foobar")
=> true
ここではユーザーのメールアドレスに example@railstutorial.org
を使用しました (図7.8)。このメールアドレスはRailsチュートリアルのロゴでも使用されています。
図7.1のモックアップに近づけるために、ユーザーのサイドバーの最初のバージョンを作りましょう。ここではaside
タグを使用して実装します。このタグはサイドバーなどの補完コンテンツの表示に使用されますが、単独で表示することもできます。row
クラスとcol-md-4
クラスも追加しておきます。これらのクラスはBootstrapの一部です。ユーザー表示ページを変更した結果をリスト7.10に示します。
show
ビューにサイドバーを追加する app/views/users/show.html.erb
<% provide(:title, @user.name) %>
<div class="row">
<aside class="col-md-4">
<section class="user_info">
<h1>
<%= gravatar_for @user %>
<%= @user.name %>
</h1>
</section>
</aside>
</div>
HTML要素とCSSクラスを配置したことにより、プロフィールページ (とサイドバーとGravatar) にSCSSでリスト7.11のようにスタイルを与えることができるようになりました7。(テーブルCSSのルールがネスティング (入れ子) されていますが、これが有効になるのはAsset PipelineでSassエンジンが使用されている場合に限られます) 。ページの変更の結果を図7.9に示します。
.
.
.
/* sidebar */
aside {
section.user_info {
margin-top: 20px;
}
section {
padding: 10px 0;
margin-top: 20px;
&: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;
}
.gravatar_edit {
margin-top: 15px;
}
7.2 ユーザー登録フォーム
ここまででユーザープロファイルページがひとまず動作するようになりましたので、今度はユーザー登録フォームを作成しましょう。図5.9 (図7.10にも再録) に示したとおり、ユーザー登録ページはまだ空白のままなので、このままではユーザー登録できません。この節の目標は、このみっともないページを改造して図7.11のモックアップのようなページに変えることです。
Web経由でユーザーを作成する機能をこれから追加しますので、6.3.4で作成したユーザーをここで削除しておきましょう。最も簡単な方法は、Rakeのdb:migrate:reset
タスクを実行してデータベースをリセットすることです。
$ bundle exec rake db:migrate:reset
最後に、システムによっては変更を反映するためにターミナル上でCtrl-Cを押してWebサーバーを再起動する必要が生じることもあります。
7.2.1 form_forを使用する
ユーザー登録ページで重要な点は、ユーザー登録に欠かせない情報を入力するためのformです。これを行うには、Railsでform_for
ヘルパーメソッドを使用します。このメソッドはActive Recordオブジェクトを取り込み、オブジェクトの属性を使用してフォームを構成します。
ユーザー登録ページ /signup のルーティングは、Usersコントローラーのnew
アクションに既に紐付けられていることを思い出してください (リスト5.33)。したがって、次のステップは、 form_for
の引数で必要となるUserオブジェクトを作成することになります。必要となる@user
変数の定義は、以下のリスト7.12のようになります。
new
アクションに@user
変数を追加する app/controllers/users_controller.rb
class UsersController < ApplicationController
def show
@user = User.find(params[:id])
end
def new
@user = User.new
end
end
フォームそのものはリスト7.13で示します。7.2.2で詳細について触れますが、まずはリスト7.14のSCSSで見栄えを整えてみましょう。box_sizing
ミックスインをリスト7.2から再利用していることに注目してください。これらのCSSルールが一度適用されると、ユーザー登録ページは図7.12のようになります.
<% provide(:title, 'Sign up') %>
<h1>Sign up</h1>
<div class="row">
<div class="col-md-6 col-md-offset-3">
<%= form_for(@user) do |f| %>
<%= f.label :name %>
<%= f.text_field :name %>
<%= f.label :email %>
<%= f.email_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-primary" %>
<% end %>
</div>
</div>
.
.
.
/* forms */
input, textarea, select, .uneditable-input {
border: 1px solid #bbb;
width: 100%;
margin-bottom: 15px;
@include box_sizing;
}
input {
height: auto !important;
}
7.2.2 フォームHTML
リスト7.13で定義したフォームを理解するために、小さなコードに分けて考えてみましょう。まずは、埋め込みRubyが使われているform_for
からend
までの外側の構造を読み解いていきます。
<%= form_for(@user) do |f| %>
.
.
.
<% end %>
do
キーワードは、 form_for
が1つの変数を持つブロックを取ることを表します。この変数f
は “form” のfです。
通常、Railsヘルパーを使用している場合、実装の詳細について知っておく必要はありません。ただしf
というオブジェクトが何をするのかは知っておく必要があります。このf
オブジェクトは、HTMLフォーム要素 (テキストフィールド、ラジオボタン、パスワードフィールドなど) に対応するメソッドが呼び出されると、@user
の属性を設定するために特別に設計されたHTMLを返します。つまり、以下のコードを実行すると、
<%= f.label :name %>
<%= f.text_field :name %>
Userモデルのname
属性を設定する、ラベル付きテキストフィールド要素を作成するのに必要なHTMLを作成します
生成されたフォームのHTMLを見たい場合は、ブラウザ上で表示画面を右クリックし、出てきたポップアップ項目の中から [ソースを表示] といった項目をクリックしてください。WebページのHTMLソースはリスト7.15のようになります。HTMLソースの中の、フォームを形成するHTML構造に注目してみましょう。
<form accept-charset="UTF-8" action="/users" class="new_user"
id="new_user" method="post">
<input name="utf8" type="hidden" value="✓" />
<input name="authenticity_token" type="hidden"
value="NNb6+J/j46LcrgYUC60wQ2titMuJQ5lLqyAbnbAUkdo=" />
<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="email" />
<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-primary" name="commit" type="submit"
value="Create my account" />
</form>
まずはこのHTMLソースの内部構造について説明します。リスト7.13とリスト7.15をじっくり見比べてみると、以下の埋め込みRubyは
<%= f.label :name %>
<%= f.text_field :name %>
以下のHTMLを生成していることがわかります。
<label for="user_name">Name</label>
<input id="user_name" name="user[name]" type="text" />
また、以下の埋め込みRubyは
<%= f.label :email %>
<%= f.email_field :email %>
以下のHTMLを生成していることがわかります。
<label for="user_email">Email</label>
<input id="user_email" name="user[email]" type="email" />
さらに、以下のコードは
<%= f.label :password %>
<%= f.password_field :password %>
以下のHTMLを生成していることがわかります。
<label for="user_password">Password</label>
<input id="user_password" name="user[password]" type="password" />
次の図7.13に示すように、テキストフィールド (type="text"
とtype="email"
) では内容をそのまま表示していますが、パスワードフィールド (type="password"
) ではセキュリティ上の目的のために文字が隠蔽されています (図7.13)。(emailフィールドとtextフィールドは同じように見えますが、細かな点が違います。たとえば、type="email"
となっている場合、モバイル端末から入力フォームをタップすると、メールアドレスに最適化された特別なキーボードが表示されるようになります。)
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つの節で説明します。
ところで、form
タグの内側で次のようなHTMLが生成されていたことにもお気付きでしょうか。
<div style="display:none">
<input name="utf8" type="hidden" value="✓" />
<input name="authenticity_token" type="hidden"
value="NNb6+J/j46LcrgYUC60wQ2titMuJQ5lLqyAbnbAUkdo=" />
</div>
このコードはブラウザ上では何も表示しませんが、Railsの内部で使用される特別なコードです。 したがって、どういった意図で生成されたのかは、現時点ではまだ理解しなくても大丈夫です。簡潔にまとめると、Unicode文字の「✓
(チェックマーク ✓)」を使ってブラウザが正しい文字コードで送信できるようにしたり、 Cross-Site Request Forgery (CSRF)と呼ばれる攻撃を阻止するために信頼できるトークンを含めたりしています8。
7.3 ユーザー登録失敗
図7.12ではフォームのHTMLがどうなっているかを簡単に説明しました (リスト7.15参照) が、フォームを理解するにはユーザー登録の失敗のときが最も参考になります。この節では、無効なデータ送信を受け付けるユーザー登録フォームを作成し、ユーザー登録フォームを更新してエラーの一覧を表示します。このモックアップを図7.14に示します。
7.3.1 正しいフォーム
7.1.2で、resources :users
をroutes.rb
ファイルに追加すると (リスト7.3) 自動的にRailsアプリケーションが表7.1のRESTful URLに応答するようになったことを思い出してください。特に、/usersへのPOSTリクエストはcreate
アクションに送られます。私たちはここで、create
アクションでフォーム送信を受け取り、User.new
を使用して新しいユーザーオブジェクトを作成し、ユーザーを保存 (または保存に失敗) し、再度の送信用のユーザー登録ページを表示するという方法で機能を実装しようと思います。まずはユーザー登録フォームのコードを見直してみましょう。
<form action="/users" class="new_user" id="new_user" method="post">
7.2.2で説明したように、このHTMLはPOSTリクエストを/usersというURLに送信します。
ユーザー登録フォームを動かすために、まずリスト7.16にようにコードを追加するところから始めます。このリストでは、5.1.3の「パーシャル」のところでも使ったrender
メソッドを再度使いまわしています。render
はコントローラのアクションの中でも正常に動作します。ここで、以前に説明したif
-else
分岐構造を思い出してください。この文を使用して、保存が成功したかどうかに応じて@user.save
の値がtrue
またはfalse
(6.1.3) になるときに、それぞれ成功時の処理と失敗時の処理を場合分けすることができます。
create
アクション app/controllers/users_controller.rb
class UsersController < ApplicationController
def show
@user = User.find(params[:id])
end
def new
@user = User.new
end
def create
@user = User.new(params[:user]) # 実装は終わっていないことに注意!
if @user.save
# Handle a successful save.
else
render 'new'
end
end
end
コメントにもあるように、上のコードはまだ実装が完了していませんので注意してください。しかし実装の出発点としてはこれで十分です。なお、最終的な実装は7.3.2で完了します。
リスト7.16のコードの動作を理解するもっともよい方法は、実際に無効なユーザー登録データを送信 (submit)してみることです。結果を図7.15に、また、すべてのデバッグ情報を図7.16に示しました (読みやすいようにフォントサイズを拡大しています)。(図7.15の下部に見えているのがRailsのweb consoleという機能です。これはrails consoleをブラウザ上で開けるようにし、デバッグをしやすくするための機能です。たとえばUserモデルを調べたいときなどには便利ですが、今のところは params
の中身を精査するなどの込み入ったことはできません。)
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="email" />
"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を参照)。これにより、デフォルトでのセキュリティが高められました。
7.3.2 Strong parameters
4.4.5で、マスアサインメントの概念について簡単に説明しました。これは、以下のように値のハッシュを使用してRubyの変数を初期化するものです。
@user = User.new(params[:user]) # 実装は終わっていないことに注意!
リスト7.16のコメントと、上の再録コメントでも重ねて指摘しているように、この実装は最終形ではありません。その理由は、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
という外部メソッドを使用するのが慣習になっています。このメソッドは適切に初期化したハッシュを返し、params[:user]
の代わりとして使用されます。
@user = User.new(user_params)
このuser_params
メソッドはUsersコントローラの内部でのみ実行され、Web経由で外部ユーザーにさらされる必要はないため、リスト7.22に示すようにRubyのprivate
キーワードを使って外部から使用できないようにします (private
キーワードの詳細については 8.4で説明します)。
create
アクションでStrong Parametersを使用する app/controllers/users_controller.rb
class UsersController < ApplicationController
.
.
.
def create
@user = User.new(user_params)
if @user.save
# Handle a successful save.
else
render 'new'
end
end
private
def user_params
params.require(:user).permit(:name, :email, :password,
:password_confirmation)
end
end
ちなみに、private
キーワード以降のコードを強調するために、user_params
のインデントを1段深くしてあります。(経験的にはこれは賢い慣習だと思います。というのも、クラス内に多数のメソッドがある場合、privateメソッドの場所が簡単に見つかるからです。これにより、インデントが無い場合と比べて、どこからprivateになるのか困惑することがなくなります。)
この時点で、(送信ボタンを押してもエラーが出ないという意味で) ユーザー登録フォームは動くようになります。ただし図7.17が示すように、(開発者用のデバッグ領域を除いて) 間違った送信をしても何もフィードバックが返ってきていません。これはユーザーが困惑する原因となります。また、有効なユーザー情報を送信しても新しいユーザーが実際に作成されることもありません。前者の問題を7.3.3で、後者の問題を7.4でそれぞれ解決していきます。
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.16で保存に失敗すると、@user
オブジェクトに関連付けられたエラーメッセージの一覧が生成されます。このメッセージをブラウザで表示するには、ユーザーのnew
ページでエラーメッセージのパーシャル (partial) を出力します。このとき、form-control
というCSSクラスも一緒に追加することで、Bootstrapがうまく取り扱ってくれるようになります。変更の結果をリスト7.18に示します。ここで使用しているエラーメッセージのパーシャルはあくまで試作品である点に注意してください。最終版は11.3.2を参照してください。
<% provide(:title, 'Sign up') %>
<h1>Sign up</h1>
<div class="row">
<div class="col-md-6 col-md-offset-3">
<%= form_for(@user) do |f| %>
<%= render 'shared/error_messages' %>
<%= f.label :name %>
<%= f.text_field :name, class: 'form-control' %>
<%= f.label :email %>
<%= f.email_field :email, class: 'form-control' %>
<%= f.label :password %>
<%= f.password_field :password, class: 'form-control' %>
<%= f.label :password_confirmation, "Confirmation" %>
<%= f.password_field :password_confirmation, class: 'form-control' %>
<%= f.submit "Create my account", class: "btn btn-primary" %>
<% end %>
</div>
</div>
ここでは、’shared/error_messages’
というパーシャルをrender
(レンダリング) している点に注目してください。これはRails全般の慣習で、パーシャルは複数のコントローラにわたるビューに対し、専用のshared/
ディレクトリを使用するようにしています(これは9.1.1で実現します)。ただし、今はまだapp/views/shared
といったディレクトリは作っていないので、表1.1で紹介したmkdir
コマンドを使い、新しくディレクトリを作成する必要があります。
$ mkdir app/views/shared
また、いつものようにテキストエディタを使ってパーシャル (_error_messages.html.erb
) も作成します。パーシャルの内容はリスト7.19のようになります。
<% if @user.errors.any? %>
<div id="error_explanation">
<div class="alert alert-danger">
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の配列に対してもそのまま使用できます。これは11.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"
のような英語の文法に合わない文字列を避けることができます (これはWeb上でどうしようもないほどよく見かけるエラーです)。
リスト7.24には、エラーメッセージにスタイルを与えるためのCSS id error_explanation
も含まれていることに注目してください (5.1.2のCSSでスタイルidに「#
」記号を使用していることも思い出してください)。さらにRailsは、無効な内容で送信がされて元のページに戻されると、div
で囲まれたエラー用のCSSクラスfield_with_errors
を返します。これらのラベルによって、リスト7.20のようにエラーメッセージをSCSSで整形することができます。ここでは、Sassの@extend
関数を使ってBootstrapのhas-error
というCSSクラスを適用してみます。
.
.
.
/* forms */
.
.
.
#error_explanation {
color: red;
ul {
color: red;
margin: 0 0 30px 0;
}
}
.field_with_errors {
@extend .has-error;
.form-control {
color: $state-danger-text;
}
}
リスト7.18とリスト7.19のコードと、SCSSのリスト7.20を組み合わせることで、無効なユーザー登録情報を送信したときのエラーメッセージが分かりやすくなります (図7.18)。これらのメッセージはモデルの検証時に生成されるので、メールアドレスのスタイルやパスワードの最小文字列などを変更すると、メッセージも自動的に変更されます。
(このとき、存在性のバリデーションもhas_secure_passwordによるバリデーションも空のパスワードを検知してしまうため、ユーザー登録フォームで空のパスワードを入力すると2つの同じエラーメッセージが表示されてしまいます。もちろんこういった冗長なエラーメッセージを直接修正することも可能ですが、幸運にも今回の場合は、後ほど追加する allow_nil: true というオプションでこの問題は解決できます。)
7.3.4 失敗時のテスト
完全なテスト機能を備えた強力なWebフレームワークがなかった頃は、開発者はフォームのテストを毎回手動で行う必要がありました。たとえば、もし仮にユーザー登録ページを手動でテストしなければならないとしたら、ブラウザでそのページを表示し、有効なデータと無効なデータを交互に流しこみ、どちらの場合にもアプリケーションが正常に動作することを確認しなければならないでしょう。さらに、アプリケーションに変更が生じるたびに、まったく同じテストを繰り返さなければなりません。このプロセスは苦痛で、バグも発生しがちです。
しかし幸運なことに、Railsではフォーム用のテストを書くことができ、こういったプロセスを自動化することができます。本項では、無効な送信をしたときの正しい振る舞いについてテストを書いていきます。7.4.4では同様の方法で、有効な送信をしたときの正しい振る舞いについてテストを書いていきます。
まずは、新規ユーザー登録用の統合テストを生成するところから始めていきます。コントローラーの慣習である「リソース名は複数形」に因んで、統合テストのファイル名はusers_signup
とします。
$ rails generate integration_test users_signup
invoke test_unit
create test/integration/users_signup_test.rb
(7.4.4で書くテストでも、ここで生成したファイルを使います)
このテストでは、ユーザー登録ボタンを押したときに (ユーザー情報が無効であるために) ユーザーが作成されないことを確認します。(なお、エラーメッセージに対するテストは7.7の演習に残しておきます。)これを確認するには、ユーザーのcountを使用します。背後で動作するこのcount
メソッドは、User
を含むあらゆるActive Recordクラスで使用できます。
$ rails console
>> User.count
=> 0
7.2の冒頭でデータベースをリセットしてあるので、現時点ではUser.count
は0
になっています。5.3.4のように、 assert_select
を使って関連ページのHTML要素をテストしていきます。これにより、今後うっかり要素を変更してしまっても気付けるようになります。
まずはget
メソッドを使ってユーザー登録ページにアクセスします。
get signup_path
フォーム送信をテストするためには、 users_path
に対してPOSTリクエストを送信する必要があります (表7.1)。これは、次のようにpost
関数を使って実現できます
assert_no_difference 'User.count' do
post users_path, user: { name: "",
email: "user@invalid",
password: "foo",
password_confirmation: "bar" }
end
create
アクションのUser.new
(リスト7.16)で期待されているデータを、params[:user]
というハッシュにまとめています。また、 assert_no_difference
メソッドのブロック内でpost
関数を使い、メソッドの引数には’User.count’
を与えています。これは、 assert_no_difference
のブロックを実行する前後で引数の値 (User.count
) が変わらないことをテストしています。すなわちこのテストは、ユーザ数を覚えた後に、データを投稿してみて、ユーザ数が変わらないかどうかを検証するテストになります。したがって、以下のコードと等価になります。
before_count = User.count
post users_path, ...
after_count = User.count
assert_equal before_count, after_count
これらのコードは等価ではありますが、assert_no_difference
を使う方が明瞭で、Rubyの慣習的にも正しいです。
また、上のコードではget
関数を使っていないことにも注目してください。これは各関数に技術的な関連性がなく、ユーザー登録ページにアクセスしなくても、直接post
関数を呼び出してユーザー登録ができることを意味しています。個人的には、コンセプトを明確にする意味とユーザー登録ページをダブルチェックする意味も兼ねて、 (実際の手順に倣って) 両方の関数を呼び出す方が好きです。
上記のアイデアをコードに落とし込むと、リスト7.21のようになります。なお、送信に失敗したときにnew
アクションが再描画されるはずなので、assert_template
を使ったテストも含めていることに注意してください。エラーメッセージが正しく表示されているかどうかについては、演習として残しておきます (7.7)。
require 'test_helper'
class UsersSignupTest < ActionDispatch::IntegrationTest
test "invalid signup information" do
get signup_path
assert_no_difference 'User.count' do
post users_path, user: { name: "",
email: "user@invalid",
password: "foo",
password_confirmation: "bar" }
end
assert_template 'users/new'
end
end
アプリケーションコードは既に実装済みなので、今回の統合テストも含め、全てのテストがGREENになるはずです。
$ bundle exec rake test
7.4 ユーザー登録成功
無効なフォームの送信を扱えるようになったので、いよいよ新規ユーザーを実際にデータベースに保存できるようにし (もちろんフォームが有効な場合に)、ユーザー登録フォームを完成させましょう。まずは、ユーザーを保存できるようにします。保存に成功すると、ユーザー情報は自動的にデータベースに登録されます。次にブラウザの表示をリダイレクトして、登録されたユーザーのプロファイルを表示します。ついでにウェルカムメッセージも表示しましょう。モックアップを図7.19に示します。保存に失敗した場合は、単に7.3で開発したとおりの動作が実行されます。
7.4.1 登録フォームの完成
ユーザー登録フォームを完成させるために、リスト7.17のコメントアウトされた部分にコードを書き、適切に動作するようにしましょう。現状では、有効な情報で送信するとエラーが発生してしまいます。図7.20が示すように、Railsのデフォルトのアクションは対応するビューを表示するようになっています。しかしcreate
アクションに対応するビューのテンプレートがないため (あるはずがありません)、このようなエラーが発生しています。
ユーザー登録に成功した場合は、ページを描画するのではなく別のページにリダイレクトするようにしてみましょう。ルートURLにリダイレクトしてもよいですが、一般的な慣習にしたがって、新しく作成されたユーザーのプロフィールページにリダイレクトしてみます。実際のアプリケーションコードをリスト7.23に示します (redirect_to
メソッドに注目してください)。
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
ここで
redirect_to @user
といった行がありますが、これは次のコードと等価になります。
redirect_to user_url(@user)
これはRailsが、redirect_to @user
というコードからuser_url(@user)
といったコードを実行したいことを(自動的に)推察してくれた結果になります。
7.4.2 flash
リスト7.23のコードによって、ユーザー登録フォームが実際に動くようになりました。これでブラウザから正しいユーザー情報を登録できるようになりましたが、その前にWebアプリケーションに常識的に備わっている機能を追加してみましょう。登録完了後に表示されるページにメッセージを表示し (この場合は新規ユーザーへのウェルカムメッセージ)、2度目以降にはそのページにメッセージを表示しないようにするというものです。
Railsでこういった情報を表示するためには、flashという特殊な変数を使います。この変数はハッシュのように扱います。Railsの一般的な慣習に倣って、:success
というキーには成功時のメッセージを代入するようにします (リスト7.24)。
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
flash
変数に代入したメッセージは、リダイレクトした直後のページで表示できるようになります。今回はflash
内に存在するキーがあるかを調べ、もしあればその値 (メッセージ) を全て表示するように、レイアウトを修正します。4.3.3でコンソール上で実行した例を思い出してみてください。そこではあえてflash
と名付けたハッシュを使用してハッシュの値を列挙しました。
$ rails console
>> flash = { success: "It worked!", danger: "It failed." }
=> {:success=>"It worked!", danger: "It failed."}
>> flash.each do |key, value|
?> puts "#{key}"
?> puts "#{value}"
>> end
success
It worked!
danger
It failed.
上で示したパターンに則って、flash変数の内容をWebサイト全体にわたって表示できるようにすると、次のようなコードになります。
<% flash.each do |message_type, message| %>
<div class="alert alert-<%= message_type %>"><%= message %></div>
<% end %>
なお、このコードではHTMLとERbが雑に混ざっています。これをキレイに整形する課題は演習として残しておきます (7.7)。さて、次の埋め込みRubyでは
alert-<%= message_type %>
適用するCSSクラスをメッセージの種類によって変更するようにしています。これにより、たとえば:success
キーのメッセージが表示される場合、適用されるCSSクラスは次のようになります。
alert-success
(:success
キーはシンボルですが、テンプレート内に反映させる直前で、埋め込みRubyが自動的に"success"
という文字列に変換しています。)これにより、キーの内容によって異なったCSSクラスを適用させることができ、メッセージの種類によってスタイルを動的に変更させることができます。たとえば、8.1.4ではflash[:error]
を使用してログインに失敗したことを表すメッセージを表示します10。(実際、既にalert-danger
というCSSクラスを使って、リスト7.19のエラーメッセージのスタイルをdivタグで指定しています。)Bootstrap CSSは、このようなflashのクラス用に4つのスタイルを持っています (success
、info
、warning
、danger
)。また、本書のサンプルアプリケーションでは、これらの全てのスタイルを場合に応じて使っていきます。
テンプレート内にflashのメッセージが差し込まれるので、次のようなコードは、
flash[:success] = "Welcome to the Sample App!"
最終的には次のようなHTMLになります。
<div class="alert alert-success">Welcome to the Sample App!</div>
先ほど説明した埋め込みRubyをレイアウトに埋め込んだ結果を、リスト7.25に示します。
flash
変数の内容をWebサイトのレイアウトに追加する app/views/layouts/application.html.erb
<!DOCTYPE html>
<html>
.
.
.
<body>
<%= render 'layouts/header' %>
<div class="container">
<% flash.each do |message_type, message| %>
<div class="alert alert-<%= message_type %>"><%= message %></div>
<% end %>
<%= yield %>
<%= render 'layouts/footer' %>
<%= debug(params) if Rails.env.development? %>
</div>
.
.
.
</body>
</html>
7.4.3 実際のユーザー登録
ついにユーザー登録が完成しました。名前を “Rails Tutorial”、メールアドレスを “example@railstutorial.org”として登録してみましょう (図7.21)。登録結果 (図7.20)にはユーザー登録成功を示すウェルカムメッセージが、success
クラスのさわやかな緑色の背景で表示されています。このクラスは5.1.2のBootstrap CSSフレームワークのものです。もしメールアドレスが既に使用されているというメッセージが表示されたら、7.2でやったようにRakeのdb:migrate:reset
を実行してデータベースをリセットしてください。ユーザー表示ページを再度読み込むと、今度はフラッシュメッセージは表示されなくなりました (図7.23)。
今度はデータベースを覗いて、新規ユーザーが確かに登録されていることをダブルチェックしましょう。
$ rails console
>> User.find_by(email: "example@railstutorial.org")
=> #<User id: 1, name: "Rails Tutorial", email: "example@railstutorial.org",
created_at: "2014-08-29 19:53:17", updated_at: "2014-08-29 19:53:17",
password_digest: "$2a$10$zthScEx9x6EkuLa4NolGye6O0Zgrkp1B6LQ12pTHlNB..."=> 6
7.4.4 成功時のテスト
次に進む前に、ここで一旦、有効な送信に対するテストを書いてみます。これによって、アプリケーションの振る舞いを検証し、もし今後バグが埋め込またらそれを検知できるようになります。7.3.4で書いた無効な送信に対するテストと同様に、今回の目的はデータベースの中身が正しいかどうか検証することです。すなわち、有効な情報を送信して、ユーザーが作成されたことを確認します。リスト7.21のときと同じは、次のようにテストを書きましたが
assert_no_difference 'User.count' do
post users_path, ...
end
今回はassert_difference
というメソッドを使ってテストを書きます。
assert_difference 'User.count', 1 do
post_via_redirect users_path, ...
end
assert_no_difference
と同様に、このメソッドは第一引数に文字列 (’User.count’
) を取り、assert_difference
ブロック内の処理を実行する直前と、実行した直後のUser.count
の値を比較します。第二引数はオプションですが、ここには比較した結果の差異 (今回の場合は1) を渡します。
リスト7.21と同じファイルにassert_difference
を使ったテストを追加すると、リスト7.26のようになります。ここで、users_pathにPOSTリクエストを送信するために、post_via_redirect
というメソッドを使っていることに注目してください。このメソッドは、POSTリクエストを送信した結果を見て、指定されたリダイレクト先に移動するメソッドです。したがって、この行の直後では’users/show’
テンプレートが表示されているはずです。ちなみに、ここにflashのテストも追加しておくとよいでしょう。これは演習として残しておきます (7.7)。
require 'test_helper'
class UsersSignupTest < ActionDispatch::IntegrationTest
.
.
.
test "valid signup information" do
get signup_path
assert_difference 'User.count', 1 do
post_via_redirect users_path, user: { name: "Example User",
email: "user@example.com",
password: "password",
password_confirmation: "password" }
end
assert_template 'users/show'
end
end
リスト7.26では、ユーザー登録に成功させた後に、どのテンプレートが表示されているのか検証していることにも注目してください。このテストがパスするためには、Userのルート(リスト7.3)とUserの show
アクション(リスト7.5)、そしてshow.html.erb
ビュー(リスト7.8)がそれぞれ正しく動いている必要があります。最後に、
assert_template 'users/show'
上のコードでは、ユーザープロフィールに関するほぼ全て (たとえばページにアクセスしたらなんらかの理由でエラーが発生しないかどうかなど) をテストできていることに注目してください。この類のエンドツーエンドテストは、アプリケーションの重要な機能をカバーしてくれています。こういった理由が統合テストが便利だと呼ばれる所以です。
7.5 プロのデプロイ
ユーザー登録ページを動かすことができたので、このアプリケーションをデプロイして、本番環境でも動かせるようにしてみましょう。第3章からデプロイをして来ましたが、実際にデータを操作できるようにするデプロイは初めてです。そこで、この機会にプロレベルのデプロイ方法について説明していきます。具体的には、ユーザー登録をセキュアにするために、本番用のアプリケーションに重要な機能を追加していきます。その後、デフォルトのWebサーバを実際の世界で使われているWebサーバに置き換えていきます。
デプロイの下準備として、まずはこの時点までの変更をmaster
ブランチにマージしておいてください。
$ git add -A
$ git commit -m "Finish user signup"
$ git checkout master
$ git merge sign-up
7.5.1 本番環境でのSSL
本章で開発したユーザー登録フォームで送信すると、名前やメールアドレス、パスワードといったデータがネットワーク越しに流されていきます。実は、このようなネットワークに流れるデータは途中で捕捉できるため、扱いには注意が必要です。これはサンプルアプリケーションの本質的なセキュリティ上の欠陥です。そしてこれを修正するためにSecure Sockets Layer (SSL)11を使います。これはローカルのサーバからネットワークに流れる前に、大事な情報を暗号化する技術です。今回はユーザー登録ページのためだけにSSLを導入しますが、これはWebサイト全体で適用できるため、第8章で実装するログイン機構をセキュアにしたり、8.4で説明するセッションハイジャックの脆弱性に対しても多くの利点を生み出します。
そしてSSLを有効化するのも簡単です。 production.rb
という本番環境の設定ファイルの1行を修正するだけで済みます。 具体的には、 config
変数で「本番環境ではSSLを強制する」という設定をするだけです (リスト7.27)。
Rails.application.configure do
.
.
.
# Force all access to the app over SSL, use Strict-Transport-Security,
# and use secure cookies.
config.force_ssl = true
.
.
.
end
次に、遠隔にあるサーバーのSSLをセットアップします。本番用のWebサイトでSSLを使えるようにするためには、ドメイン毎にSSL証明書を購入し、セットアップする必要があります。これには多くの作業が必要となりますが、幸運にもそういった作業をしなくても済む方法があります。それは、Heroku上でサンプルアプリケーションを動かし、HerokuのSSL証明書に便乗する方法です (訳注: ただし、この方法はHerokuのサブドメインでのみ有効です。独自ドメインを使う場合はSSL証明書を購入する必要があります)。結果として、7.5.2でアプリケーションのデプロイが終わると、自動的にSSLが有効化されているはずです。(もしwww.example.com
などの独自ドメインでSSLを使いたい場合は、Heroku’s page on SSL (英語) の記事を参照してください。)
7.5.2 本番環境用Webサーバー
SSLを導入したので、次はアプリケーションの設定をいじって、本番環境に適したWebサーバを使ってみましょう。Herokuのデフォルトでは、Rubyだけで実装されたWEBrickというWebサーバを使っています。WEBrickは簡単にセットアップできたり動せることが特長ですが、著しいトラフィックを扱うことには適していません。つまり、WEBrickは本番環境として適切なWebサーバではありません。よって、今回はWEBrickをPumaというWebサーバに置き換えてみます。Pumaは多数のリクエストを捌くことに適したWebサーバです。
新しいWebサーバを追加するために、Heroku内のPumaドキュメント (英語) にしたがってセットアップしていきます。まずはpuma gemをGemfile
に追加します (リスト7.28)。このとき、ローカル環境 (開発用の環境) でPumaを使う必要はないので、リスト7.28のように:production
グループの中に追加しておきます。
Gemfile
にPumaを追加する
source 'https://rubygems.org'
.
.
.
group :production do
gem 'pg', '0.17.1'
gem 'rails_12factor', '0.0.2'
gem 'puma', '2.11.1'
end
Bundlerでは本番環境用のgemはインストールしない設定にしておいたので (3.1)、リスト7.28は開発環境に影響はありません。しかし、BundlerにGemfile.lock
を更新してもらう必要があるので、いつものように次のコマンドを実行しておきます。
$ bundle install
次のステップは、config/puma.rb
というファイルを作成し、そこにリスト7.29のような設定情報を追加します。リスト7.29はHerokuのドキュメント12をそのまま引用したコードです。これらのコードは理解しなくても大丈夫です。
workers Integer(ENV['WEB_CONCURRENCY'] || 2)
threads_count = Integer(ENV['MAX_THREADS'] || 5)
threads threads_count, threads_count
preload_app!
rackup DefaultRackup
port ENV['PORT'] || 3000
environment ENV['RACK_ENV'] || 'development'
on_worker_boot do
# Worker specific setup for Rails 4.1+
# See: https://devcenter.heroku.com/articles/
# deploying-rails-applications-with-the-puma-web-server#on-worker-boot
ActiveRecord::Base.establish_connection
end
最後に、Procfile
と呼ばれる、Heroku上でPumaのプロセスを走らせる設定ファイルを作成します (リスト7.30)。なお、このProcfile
はルートディレクトリ (Gemfile
と同じディレクトリ) に置いておく必要があるので、ファイルの置き場所には注意してください。
Procfile
で定義する ./Procfile
web: bundle exec puma -C config/puma.rb
これで、本番環境用のWebサーバの設定は完了しました。これらの変更をコミットし、デプロイしてみましょう13。
$ bundle exec rake test
$ git add -A
$ git commit -m "Use SSL and the Puma webserver in production"
$ git push
$ git push heroku
$ heroku run rake db:migrate
ユーザー登録フォームが無事に動いたら成功です。成功すると図7.24のようになります。このとき、URLがhttps://に変わっていて、アドレスバーに鍵アイコンが表示されていることにも注目してください (図7.24)。これは先ほど設定したSSLがうまく動いていることを示しています。
7.5.3 Rubyのバージョン番号
ところで、Herokuのデプロイするとき、もしかしたら次のような警告メッセージを目にしたことがあるかもしれません。
###### WARNING:
You have not declared a Ruby version in your Gemfile.
To set your Ruby version add this line to your Gemfile:
ruby '2.1.5'
(これは「Rubyのバージョンを明示的に指定してください」というメッセージですが) 経験的には、本書のようなチュートリアルの段階では明示的に指定しない方がスムーズに進むことが多いので、この警告は現時点では無視してしまった方がよいでしょう。というのも、サンプルアプリケーションでRubyのバージョンを常に最新に保っておくと、多大な不都合に繋がりかねないからです14。また、本書のサンプルアプリケーションにおいては、ローカルで使っているバージョンと本番環境のバージョンが異なっていても、違い生じることはほぼ無いでしょう。とは言うものの、次の点は頭の片隅に置いておいてください。それは、仕事でHerokuを使ったアプリケーションを動かす場合はGemfile
でRubyのバージョンを明示しておいた方が賢明である、という点です。これによって開発環境と本番環境の互換性を最大限に高めることができるので、(バージョンの差異による誤作動やエラーなどが無くなり) お勧めです。
7.6 最後に
ユーザー登録機能の実装は、私たちのサンプルアプリケーションにとって大きなマイルストーンでした。この時点でサンプルアプリケーションはかなり実用的になってきましたが、まだ重要な機能がいくつも残っています。第8章では、認証 (authentication) システムを導入し、ユーザーがログインとログアウトをできるようにします。第9章では、どのユーザーも自分のアカウント情報を更新できるようにし、Webサイトの管理者がユーザーを削除できるようにします。それにより、Usersリソースに表7.1のRESTアクションがすべて実装されるようにします。
7.6.1 本章のまとめ
-
debug
メソッドを使うことで、役立つデバッグ情報を表示できる - Sassのmixin機能を使うと、CSSのルールをまとめたり他の場所で再利用できるようなる
- Railsには標準で3つ環境が備わっており、それぞれ
開発環境 (development)
、テスト環境 (test)
、本番環境 (production)
と呼ぶ - 標準的なRESTfulなURLを通して、ユーザー情報をリソースとして扱えるようになった
- Gravatarを使うと、ユーザーのプロフィール画像を簡単に表示できるようになる
-
form_for
ヘルパーは、Active Recordのオブジェクトに対応したフォームを生成する - ユーザー登録に失敗した場合はnewビューを再描画するようにした。その際、Active Recordが自動的に検知したエラーメッセージを表示できるようにした
-
flash
変数を使うと、一時的なメッセージを表示できるようになる - ユーザー登録に成功すると、データベース上にユーザーが追加、プロフィールページにリダイレクト、ウェルカムメッセージの表示といった順で処理が進む
- 統合テストを使うことで送信フォームの振る舞いを検証したり、バグの発生を検知したりできる
- セキュアな通信と高いパフォーマンスを確保するために、本番環境ではSSLとPumaを導入した
7.7 演習
注: 『演習の解答マニュアル (英語)』にはRuby on Railsチュートリアルブックのすべての演習の解答が掲載されており、www.railstutorial.orgで本書を購入いただいた方には無料で配布しています (訳注: 解答は英語です)。
演習とチュートリアル本編との食い違いを避ける方法については、演習用のトピックブランチに追加したメモ (3.6) を参照してください。
-
リスト7.31のコードを使用して、7.1.4で定義された
gravatar_for
ヘルパーにオプションのsize
パラメーターを取ることができる (gravatar_for user, size: 40
のようなコードをビューで使用できる) ことを確認してください。(9.3.1でこれを改善したヘルパーを使います) - リスト7.18で実装したエラーメッセージに対するテストを書いてみてください。どのくらい細かくテストするかはお任せします。リスト7.32にテンプレートを用意しておいたので、参考にしてください。
-
7.4.2で実装したflashに対するテストを書いてみてください。どのくらい細かくテストするかはお任せします。 リスト7.33に最小限のテンプレートを用意しておいたので、参考にしてください (ヒント:
FILL_IN
メソッドを適切なコードに置き換えると完成します)。(テキストに対するテストは壊れやすいです。文量の少ないflashのキーであっても、それは同じです。個人的には、flashが空でないかをテストするだけの場合が多いです) -
7.4.2で触れたように、flash用のHTML (リスト7.25) は読みにくいです。より読みやすくしたリスト7.34のコードに対してテストスイートを実行し、こちらも正常に動作することを確認してください。このコードでは、Railsの
content_tag
ヘルパーを使用しています。
gravatar_for
ヘルパーにキーワード引数を追加する app/helpers/users_helper.rb
module UsersHelper
# 引数で与えられたユーザーのGravatar画像を返す
def gravatar_for(user, options = { size: 80 })
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
require 'test_helper'
class UsersSignupTest < ActionDispatch::IntegrationTest
test "invalid signup information" do
get signup_path
assert_no_difference 'User.count' do
post users_path, user: { name: "",
email: "user@invalid",
password: "foo",
password_confirmation: "bar" }
end
assert_template 'users/new'
assert_select 'div#<CSS id for error explanation>'
assert_select 'div.<CSS class for field with error>'
end
.
.
.
end
require 'test_helper'
.
.
.
test "valid signup information" do
get signup_path
assert_difference 'User.count', 1 do
post_via_redirect users_path, user: { name: "Example User",
email: "user@example.com",
password: "password",
password_confirmation: "password" }
end
assert_template 'users/show'
assert_not flash.FILL_IN
end
end
content_tag
を使ってレイアウトの中にflash
を埋め込む app/views/layouts/application.html.erb
<!DOCTYPE html>
<html>
.
.
.
<% flash.each do |message_type, message| %>
<%= content_tag(:div, message, class: "alert alert-#{message_type}") %>
<% end %>
.
.
.
</html>
- Mockingbirdでは図7.1のプロファイル写真のようなカスタム画像はサポートされていません。ここではGIMPを使用して手動で画像を置きました。↑
- このカバの画像はhttp://www.flickr.com/photos/43803060@N00/24308857/から引用しました。↑
- 実は、この3つ以外にもカスタムの環境を作成することができます。詳細については「環境を追加した場合のRailsCast (英語)」を参照してください。↑
- Railsの
debug
情報は YAML (一種の再帰的略語であり、“YAML Ain’t Markup Language” の略とされています) 形式で表示されます。YAMLは人間だけでなくコンピュータにとっても読みやすい形式です。↑ - この時点では、ルーティングは動作していますが、対応するページが動作しているとは限りません。たとえば、/users/1/edit がUsersコントローラの
edit
アクションに正常にルーティングされているとしても、edit
アクションが存在しなければ、このURLにアクセスしたときにエラーになります。↑ - ヒンズー教では、アバターは人間や動物の形をとって神が顕現したものと考えられています。これを拡大解釈して、アバターという用語は、特にネット界隈で、その人物を表現するもの (かつその人そのものの一部でもある) という意味で使われます。↑
-
リスト7.11では
.gravatar_edit
というCSSクラスを追加しています。これは第9章でも使われます。↑ - 動作の詳細を知りたい場合は、Stack OverflowのRails信頼性トークン関連の書き込み (英語) を参照してください。↑
- 私はRails APIで
pluralize
を探し、これを見つけました。 ↑ - 実際には、これに非常に近い
flash.now
を使いますが、本当に必要になるまでは使わないようにしようかと思います。↑ - 技術上は、SSLはTLS (Transport Layer Security) と名称が変わりましたが、未だに “SSL” と呼ばれ続けています。↑
- リスト7.29では少しだけコードの見栄えを修正しています。これは標準的な1行80文字の制限に合わせるための変更です。↑
- 本章ではデータモデルに対して変更を加えていなかったので、6.4のステップが済んでいれば、本当はHeroku上でマイグレーションを実行しなくても問題ないはずです。ただし、読者からトラブル報告がいくつか来ていたので、念のため
heroku run rake db:migrate
を実行するようにしてあります。↑ - たとえば、 ローカルマシンでRuby 2.1.4がインストールできなくて何時間も過ごしてしまい、なんとか無事にインストールできたと思ったら、先日Ruby 2.1.5がリリースされたことに気付いたときなどです。ちなみにRuby 2.1.5のインストールにも苦戦しました。↑
Railsチュートリアルは YassLab 社によって運営されています。
コンテンツを継続的に提供するため、書籍・動画・質問対応サービスなどもご検討していただけると嬉しいです。
研修支援や教材連携にも対応しています。note マガジンや YouTube チャンネルも始めたので、よければぜひ遊びに来てください!