Remember meチェックボックス
永続ログインするか否かを選択できるチェックボックスを、ログイン画面に実装する。
チェックボックス用フォーム
チェックボックスはユーザー情報入力用のフォームと同じ要領で作成できる。
<%= form_for(:session, url: login_path) do |f| %>
.
.
.
<%= f.label :remember_me, class: "checkbox inline" do %>
<%= f.check_box :remember_me %>
<span>Remember me on this computer</span>
<% end %>
<%= f.submit "Log in", class: "btn btn-primary" %>
<% end %>
チェックボックスから送信される値はparams[:session][:remember_me]に入っている。
チェックすれば1、チェックを外せば0である。
1であればremember(user)メソッドを呼び出して永続ログインし、0であればforget(user)メソッドを呼び出して一時ログインするように、Sessionsコントローラのcreateアクションを変更する。
def create
user = User.find_by(email: params[:session][:email].downcase)
if user && user.authenticate(params[:session][:password])
log_in user
params[:session][:remember_me] == '1' ? remember(user) : forget(user)
redirect_to user
else
flash.now[:danger] = 'Invalid email/password combination'
render 'new'
end
end
ここで使われている三項演算子は、if文と同じ意味である。
条件式 ? 処理A : 処理B
# if-else文と同じ
if 条件式
処理A
else
処理B
end
永続ログイン機能のテスト
永続ログインがちゃんと機能するか、テストを書く。
log_in_asテストヘルパー
Sessionsコントローラのlog_inヘルパーと同じ機能を持つ、テスト用のlog_in_asヘルパーメソッドを定義する。
ただし、統合テストではsessionメソッドが使えないようなので、統合テスト用に同じ名前のメソッドを作り、共にtest/test_helper.rbに置いておく。
こうすると、単体テストか統合テストかに関わらずlog_in_asメソッドでログインをテストできる。
class ActiveSupport::TestCase
fixtures :all
# テストユーザーがログイン中の場合にtrueを返す
def is_logged_in?
!session[:user_id].nil?
end
# テストユーザーとしてログインする
def log_in_as(user)
session[:user_id] = user.id
end
end
class ActionDispatch::IntegrationTest
# テストユーザーとしてログインする(統合テスト用)
def log_in_as(user, password: 'password', remember_me: '1')
post login_path, params: { session: { email: user.email,
password: password,
remember_me: remember_me } }
end
end
第二引数にpasswordキーをわざわざつけて、テスト用ユーザーに合わせてpasswordを変えれるようにしているようだが、あまり意味はないんじゃないかと思う。
remember_meキーは値を変更できるようにすることで、チェックボックスのチェック有り無しに対応できるようにしている。
Remember meチェックボックスのテスト
チェックボックスのオンオフに対応する2つのテストを書く。
test "login with remembering" do
log_in_as(@user, remember_me: '1')
assert_not_empty cookies['remember_token']
end
test "login without remembering" do
# クッキーを保存してログイン
log_in_as(@user, remember_me: '1')
delete logout_path
# クッキーを削除してログイン
log_in_as(@user, remember_me: '0')
assert_empty cookies['remember_token']
end
チェック無しでのログインでは、一度チェック有りでログインした後ログアウトし、cookiesの値が0→1→0と変化していることを確認している。
なお、テスト内ではcookies[:remember_token]と書けず、cookies['remember_token']と書かないといけない謎の仕様があるらしい。
assignsメソッド
テスト内ではUserモデルの仮想の属性であるremember_tokenにはアクセスできないが、assignsメソッドを使うとできるようになる。
今は割愛する。
current_userのテスト
current_userメソッドでは、session[:user_id]が存在しない場合、cookies.signed[:user_id]が存在するか確認し、存在すれば該当するユーザーを探して現在のユーザーとして返していた。
また、その際にcookiesのトークンとUserオブジェクトのトークンが一致するか検証していた。
この2点をテストする。
(ここではテスト忘れを防ぐテクニックが紹介されているが、今は割愛する。)
# 記憶トークンcookieに対応するユーザーを返す
def current_user
if (user_id = session[:user_id])
@current_user ||= User.find_by(id: user_id)
elsif (user_id = cookies.signed[:user_id])
user = User.find_by(id: user_id)
if user && user.authenticated?(cookies[:remember_token])
log_in user
@current_user = user
end
end
end
Sessionsヘルパー用のテストを作成する。
$ touch test/helpers/sessions_helper_test.rb
テストは以下のようになる。
require 'test_helper'
class SessionsHelperTest < ActionView::TestCase
def setup
@user = users(:michael)
remember(@user)
end
test "current_user returns right user when session is nil" do
assert_equal @user, current_user
assert is_logged_in?
end
test "current_user returns nil when remember digest is wrong" do
@user.update_attribute(:remember_digest, User.digest(User.new_token))
assert_nil current_user
end
end
まずテスト用のユーザーにremember(user)メソッドを使い、トークンの作成とremember_digestへの保存、cookiesへのユーザーIDとトークンの保存を行なっている。
cookies[:user_id]が存在し、sessions[:user_id]が存在しない状態である。
一つ目のテストは現在のユーザーが取得でき、それがテスト用ユーザーと一致するかを確認している。
current_userにはその後ログインしてsession[:user_id]にユーザーIDを入れる処理があるので、それも確認しておく。
二つ目のテストは、テスト用ユーザーのトークンに異なるトークンを入れてみて、現在のユーザーが取得できるかを確認している。