目的
ユーザー登録機能を追加する
7.1.1 デバッグとRails環境
・サイトのレイアウトにデバッグ情報を追加
ビルトインのdebugメソッドとparams変数を使って、各プロフィールページにデバッグ用の情報が表示されるようにする
<%= debug(params) if Rails.env.development? %>
<!DOCTYPE html>
<html>
.
.
.
<body>
<%= render 'layouts/header' %>
<div class="container">
<%= yield %>
<%= render 'layouts/footer' %>
<%= debug(params) if Rails.env.development? %>
</div>
</body>
</html>
if Rails.env.development?とすることで
デバッグ情報はRailsの3つのデフォルト環境のうち、開発環境(development)だけで表示されるようになる
Railsにはテスト環境(test)、開発環境(development)、そして本番環境(production)の3つの環境がデフォルトで装備されておりRails consoleのデフォルトの環境はdevelopmentとなっている
7.1.2 Usersリソース
ユーザーのページにアクセスする
id=1のユーザーにアクセスするためのページのURIは/users/1となる
resources :users
とすることで
ユーザーのURLを生成するための多数の名前付きルーティングと共に、RESTfulなUsersリソースで必要となるすべてのアクションが利用できるようになる
Rails.application.routes.draw do
root "static_pages#home"
get "/help", to: "static_pages#help"
get "/about", to: "static_pages#about"
get "/contact", to: "static_pages#contact"
get "/signup", to: "users#new"
resources :users
end
・Railsにおける標準的な場所
app/views/users/show.html.erb
今回はユーザーを表示するために使う
ERBを使ってユーザー名とメールアドレスを表示する
<%= @user.name %>, <%= @user.email %>
ユーザー表示ビューが正常に動作するために、
Usersコントローラ内のshowアクションに対応する@user変数を定義する
class UsersController < ApplicationController
def show
@user = User.find(params[:id])
end
def new
end
end
7.1.3 debuggerメソッド
トラブルが起こっていそうなコードの近くに差し込む
エラーを追跡したりデバッグするときに非常に強力なツールになる
class UsersController < ApplicationController
def show
@user = User.find(params[:id])
debugger
end
def new
end
end
/users/1にアクセスすると
Railsコンソールのようにコマンドを呼び出すことで
デバック内容を確認できる
7.1.4 Gravatar画像とサイドバー
Gravatarを使い
プロフィール写真をアップロードして、指定したメールアドレスと関連付ける
gravatar_forヘルパーメソッドを使ってGravatarの画像を利用できるようにする
<% provide(:title, @user.name) %>
<h1>
<%= gravatar_for @user %>
<%= @user.name %>
</h1>
gravatar_forヘルパーメソッドを定義する
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.2 ユーザー登録フォーム
ユーザー登録フォームを作成する
7.2.1 form withを利用する
ユーザー登録に不可欠な情報を入力するフォームを
form_withを用いて作成する
・form_withヘルパーメソッド
Active Recordのオブジェクトを取り込み、そのオブジェクトの属性を使ってフォームを構築する
newアクションに@user変数を追加
class UsersController < ApplicationController
def show
@user = User.find(params[:id])
end
def new
@user = User.new
end
end
・新規ユーザー向けのユーザー登録フォーム
<% provide(:title, 'Sign up') %>
<h1>Sign up</h1>
<div class="row">
<div class="col-md-6 col-md-offset-3">
<%= form_with(model: @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>
7.2.2 ユーザー登録フォームのHTML
・form_withからendまでの外側の構造
doキーワードは、form_withが受け取るブロックを表す
このブロックにはfというブロック変数があり、fは “form” のこと
<%= form_with(model: @user) do |f| %>
.
.
.
<% end %>
fオブジェクトは、HTMLフォーム要素(テキストフィールド、ラジオボタン、パスワードフィールドなど)に対応するメソッドが呼び出されると、@userの属性を設定するために特別に設計されたHTMLを返す
・Userモデルのname属性を設定する
ラベル付きテキストフィールド要素を作成するのに必要なHTMLを作成する
作成されたフォームのHTML
<form action="/users" accept-charset="UTF-8" method="post"><input type="hidden" name="authenticity_token"
value="F6LQVZQ6nplry5bttSF6i3Merk8zp3-oJJ_DeH8cOEo36wQB9iybuoPtsUIFUQZAf5sGuvVJH6Ksd6qQRoMq6w" autocomplete="off" />
<label for="user_name">Name</label>
<input type="text" name="user[name]" id="user_name" />
<label for="user_email">Email</label>
<input type="email" name="user[email]" id="user_email" />
<label for="user_password">Password</label>
<input type="password" name="user[password]" id="user_password" />
<label for="user_password_confirmation">Confirmation</label>
<input type="password" name="user[password_confirmation]" id="user_password_confirmation" />
<input type="submit" name="commit" value="Create my account" class="btn btn-primary" data-disable-with="Create my account" />
</form>
それぞれの関連
<%= f.label :name %>
<%= f.text_field :name %>
↓
<label for="user_name">Name</label>
<input type="text" name="user[name]" id="user_name" />
<%= f.label :email %>
<%= f.email_field :email %>
↓
<label for="user_email">Email</label>
<input type="email" name="user[email]" id="user_email" />
<%= f.label :password %>
<%= f.password_field :password %>
↓
<label for="user_password">Password</label>
<input type="password" name="user[password]" id="user_password" />
・ユーザー作成で重要なのはinputごとにある特殊なname属性
name属性値のおかげで、Railsはparams変数から初期化ハッシュを構成できる
<input - - - name="user[name]" id="user_name" />
.
.
.
<input - - - name="user[password]" id="user_password" />
次に重要な要素は、formタグ自身
Railsはformタグを作成するときに@userオブジェクトを使う。
すべてのRubyオブジェクトは自分のクラスを知っているので、Railsは@userのクラスがUserであることを認識する。
<form action="/users" accept-charset="UTF-8" method="post">。
7.3 ユーザー登録失敗
フォームを理解する為にユーザー登録が失敗したときを考える
7.3.1 正しいフォーム
createアクションでフォーム送信を受け取り、User.newを使って新しいユーザーオブジェクトを作成し、ユーザーを保存(または保存に失敗)し、再送信できるようにユーザー登録ページを表示するという方法で機能を実装する
/usersへのPOSTリクエストはcreateアクションに送られる
def create
@user = User.new(params[:user]) # 実装は終わっていないことに注意!
if @user.save
# 保存の成功をここで扱う。
else
render 'new', status: :unprocessable_entity
end
end
・status: :unprocessable_entity
HTTPステータスコード422 Unprocessable Entityに対応するもの
Turboを用いて通常のHTMLをレンダリングする場合に必要
422 Unprocessable Entityデバック時のパラメーター
デバッグ情報のパラメーターハッシュのuser
"user" => { "name" => "Foo Bar",
"email" => "foo@invalid",
"password" => "[FILTERED]",
"password_confirmation" => "[FILTERED]"
}
このハッシュはUsersコントローラにparamsとして渡される
フォーム送信の結果が、送信された値に対応する属性とともにuserハッシュに保存されている
このハッシュのキーが、inputタグにあったname属性の値になる
@user = User.new(params[:user])
↓
@user = User.new(name: "Foo Bar", email: "foo@invalid",
password: "foo", password_confirmation: "bar")
ハッシュのキーはデバッグ情報では文字列になっているが、Railsは文字列ではなく、params[:user]のように「シンボル」としてUsersコントローラに渡している
・マスアサインメントとは
フォームから送られてきたパラメーターをひとつにまとめて、一度に保存できるRailsの機能
DBの更新系処理で複数のカラムを一括で指定できる機能
マスアサインメントのコード
@user = User.new(params[:user])
↓
@user = User.new(name: "Foo Bar", email: "foo@invalid",
password: "foo", password_confirmation: "bar")
と同義
7.3.2 Strong Parameters
@user = User.new(params[:user])
ユーザーが送信したデータをまるごとUser.newに渡していることになり危険
admin属性の管理権限が上書かれるなどのリスクがある
・Strong Parameters
必須パラメータと許可済みパラメータを指定できる
params.require(:user).permit(:name, :email, :password, :password_confirmation)
これらのパラメータはuser_paramsのような補助メソッドの形で使うのが定番
・Privateメソッドを定義する
def create
@user = User.new(user_params)
if @user.save
# 保存の成功をここで扱う。
else
render 'new', status: :unprocessable_entity
end
end
private
def user_params
params.require(:user).permit(:name, :email, :password,
:password_confirmation)
end
end
7.3.3 エラーメッセージ
ユーザー登録に失敗した場合の最後の手順として、問題が生じたためにユーザー登録が行われなかったということをユーザーにわかりやすく伝えるエラーメッセージを追加する
・エラーメッセージが出力される画面
<% provide(:title, 'Sign up') %>
<h1>Sign up</h1>
<div class="row">
<div class="col-md-6 col-md-offset-3">
<%= form_with(model: @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>
エラーメッセージのパーシャルで
@userオブジェクトに関連付けられたエラーメッセージを表示する
エラーメッセージのパーシャル
<% 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 %>
・count
エラーの個数を返す
>> user.errors.count
=> 2
・any?
empty?メソッドと逆の結果を返す。
要素が1つでもある場合はtrue、ない場合はfalseを返す
>> user.errors.empty?
=> false
>> user.errors.any?
=> true
・pluralize
第1引数に整数を渡すと、それに基づいて第2引数の英単語を複数形に変更したものを返す。
>> helper.pluralize(1, "error")
=> "1 error"
>> helper.pluralize(5, "error")
=> "5 errors"
・CSS id
error_explanation
エラーメッセージにスタイルを与える
・field_with_errors
無効な内容の送信によって元のページに戻されると、
CSSクラスfield_with_errorsを持ったdivタグでエラー箇所を自動的に囲んでくれる。
7.3.4 失敗時のテスト
ユーザー情報が無効の場合はユーザー登録ボタンを押してもユーザーが作成されないことを確認する
・フォーム用のテストを作成
フォーム送信をテストするには、 POSTリクエストをusers_pathに送信する
post users_path
↓
createアクションのUser.newで期待されているデータを、params[:user]というハッシュにまとめている
params: { user: { name: "",
email: "user@invalid",
password: "foo",
password_confirmation: "bar" } }
↓
テストの内容としては
assert_no_differenceメソッドのブロック内でpostを使い、メソッドの引数には'User.count'を与えている。assert_no_differenceのブロックを実行する前後で引数の値(User.count)が変わらないことをテストしており
ユーザー数をメモしておいてから無効なデータを投稿し、ユーザー数が変わらないかどうかを検証している
assert_no_difference 'User.count' do
テストコード
require "test_helper"
class UsersSignupTest < ActionDispatch::IntegrationTest
test "invalid signup information" do
get signup_path
assert_no_difference 'User.count' do
post users_path, params: { user: { name: "",
email: "user@invalid",
password: "foo",
password_confirmation: "bar" } }
end
assert_response :unprocessable_entity
assert_template 'users/new'
end
end
7.4 ユーザー登録成功
7.4.1 登録フォームの完成
有効な情報で送信してユーザーが登録されるようにする
class UsersController < ApplicationController
.
.
.
def create
@user = User.new(user_params)
if @user.save
redirect_to @user
else
render 'new', status: :unprocessable_entity
end
end
private
def user_params
params.require(:user).permit(:name, :email, :password,
:password_confirmation)
end
end
・redirect_to
redirect_to @user
↓
redirect_to user_url(@user)
と同義
7.4.2 flash
新規ユーザーへのウェルカムメッセージを表示
登録完了後に表示されるページにメッセージを表示し、
2度目以降にはそのページにメッセージを表示しない
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', status: :unprocessable_entity
end
end
private
def user_params
params.require(:user).permit(:name, :email, :password,
:password_confirmation)
end
end
・flash
flash変数に代入したメッセージは、リダイレクトした直後のページで表示できるようになる。
7.4.4 成功時のテスト
有効な情報を送信して、ユーザーが作成されたことを確認する
テストコード
require "test_helper"
class UsersSignupTest < ActionDispatch::IntegrationTest
.
.
.
test "valid signup information" do
assert_difference 'User.count', 1 do
post users_path, params: { user: { name: "Example User",
email: "user@example.com",
password: "password",
password_confirmation: "password" } }
end
follow_redirect!
assert_template 'users/show'
end
end
・follow_redirect!
POSTリクエストを送信した結果を見て、指定されたリダイレクト先に移動する
この行の直後では'users/show'テンプレートが表示されていることを確認している
assert_template 'users/show'
7.5.1 本番環境でのTLS
・TLS
安全性の高い通信を行うプロトコル
公開鍵認証や共通鍵暗号、ハッシュ化などの機能を提供しており、
データ送信者と受信者の間でやり取りされるデータが、第三者に盗聴・なりすましをされるリスクを防ぐ
production.rbに
config.force_ssl trueを設定する
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
7.5.2 本番環境用のWebサーバー
本番環境ではPumaというHTTPサーバーを使う
puma.rbに内容を追加
# Pumaの設定ファイル
max_threads_count = ENV.fetch("RAILS_MAX_THREADS") { 5 }
min_threads_count = ENV.fetch("RAILS_MIN_THREADS") { max_threads_count }
threads min_threads_count, max_threads_count
port ENV.fetch("PORT") { 3000 }
environment ENV.fetch("RAILS_ENV") { "development" }
pidfile ENV.fetch("PIDFILE") { "tmp/pids/server.pid" }
workers ENV.fetch("WEB_CONCURRENCY") { 4 }
preload_app!
plugin :tmp_restart
感想
今回はユーザー登録機能及びテストの実装を行いました。
C#やjavascriptのテストコードではpost,get等のhttp通信の処理も
自前でモック化する必要がありましたが、
Railsではただ処理を呼び出すだけでテストができてとても便利だなと思いました!