ログアウト
前提条件
現在開発中のサンプルアプリケーションは、「ユーザーが明示的にログアウトするまではログイン状態を保つ」という仕様になっています。一方現状では、「ユーザーが明示的にログアウトする」という機能が実装されていません。
一方で、ログアウト機能の実装の前提となる以下の要素は既に実装済みです。
- ログイン済みユーザー用ページの内容ヘッダー部における、ログアウト用のリンク
logout_path
へのDELETE
リクエストに対する、sessions#destroy
へのルーティング- Sessionsコントローラにおける、
destroy
アクションそのものの定義
これから新たに実装が必要になるのは、「Sessionsコントローラにおける、destroy
アクションの実際の動作」ということになります。
session.delete
メソッド
セッションから情報を削除するために用いるメソッドです。今回削除するのはユーザーIDなので、引数として:user_id
シンボルを取ります。
session.delete(:user_id)
上記コードを実行すると、指定したユーザーIDに対するセッションの情報をnil
にできます。
Sessionsヘルパーにlog_out
メソッドを実装する
ログアウト機能も、ログインに関連する機能と同様、様々なビューやコントローラで使われる可能性がある機能です。そのため、ログアウト機能の実体はSessionsヘルパーにlog_out
メソッドとして実装していきます。
module SessionsHelper
...略
+
+ # 現在のユーザーをログアウトする
+ def log_out
+ session.delete(:user_id)
+ @current_user = nil
+ end
end
Sessionsコントローラのdestroy
アクションを実装する
class SessionsController < ApplicationController
def new
end
def create
user = User.find_by(email: params[:session][:email].downcase)
if user && user.authenticate(params[:session][:password])
log_in user
redirect_to user
else
flash.now[:danger] = 'Invalid email/password combination'
render 'new'
end
end
def destroy
log_out
redirect_to root_url
end
end
ログアウト機能に対するテストを実装する
require 'test_helper'
class UsersLoginTest < ActionDispatch::IntegrationTest
...略
- test "login with valid information" do
+ test "login with valid information followed by logout" do
get login_path
post login_path, params: { session: { email: @user.email, password: 'password' } }
assert_redirected_to @user
follow_redirect!
assert_template 'users/show'
assert_select "a[href=?]", login_path, count: 0
assert_select "a[href=?]", logout_path
assert_select "a[href=?]", user_path(@user)
+ delete logout_path
+ assert_not is_logged_in?
+ assert_redirected_to root_url
+ follow_redirect!
+ assert_select "a[href=?]", login_path
+ assert_select "a[href=?]", logout_path, count: 0
+ assert_select "a[href=?]", user_path(@user), count: 0
end
end
テストの名前を変えた上で、以下の手順を追加しています。
logout_path
に対してdelete
アクションを実行するlogged_in?
の戻り値がfalse
になることを確認する- リダイレクト先が
root_url
であることを確認する - 実際にリダイレクト先へ移動する
- ログインリンクが存在することを確認する
- ログアウトリンクが存在しないことを確認する
- ユーザープロフィールへのリンクが存在しないことを確認する
なんかテストの粒度が大きすぎる気もします…
ログアウト機能に対するテストを実行する
ここまでの実装で、テストは通るようになっているはずです。実際にテストを実行してみましょう。
# rails test
Running via Spring preloader in process 321
Started with run options --seed 31924
24/24: [=================================] 100% Time: 00:00:02, Time: 00:00:02
Finished in 2.43459s
24 tests, 68 assertions, 0 failures, 0 errors, 0 skips
全体のテストが通りました。
- ユーザー登録
- ログイン
- ログアウト
ここまでの実装が終わりましたね。
演習 - ログアウト
1. ブラウザから [Log out] リンクをクリックし、どんな変化が起こるか確認してみましょう。また、リスト 8.31で定義した3つのステップを実行してみて、うまく動いているかどうか確認してみましょう。
ログイン済みで、ログインユーザーのプロフィールページが表示された状態から開始します。
「Log out」リンクをクリックします。rails server
のログには以下のログが記録されています。
Started DELETE "/logout" for 172.17.0.1 at 2019-11-03 13:14:29 +0000
Cannot render console from 172.17.0.1! Allowed networks: 127.0.0.1, ::1, 127.0.0.0/127.255.255.255
Processing by SessionsController#destroy as HTML
Parameters: {"authenticity_token"=>"v1pJUi79KnyRa8J1ocwc6k5iQcm2H1jLTBH+IiCqTDtq2ZoJSLJ3Bzh7sCy/C2GMy2vv6Sl6iUQC8oiA3VAT9Q=="}
Redirected to http://localhost:8080/
Completed 302 Found in 3ms (ActiveRecord: 0.0ms)
rails server
のログから、以下のことがわかります。
- エンドポイント
/logout
に対してDELETE
リクエストが送出されている - Sessionsコントローラの
destroy
アクションが実行されている - ルートのフルURLに対してリダイレクトされている
- 最終的に戻ってきたHTTPステータスコードは
302 Found
である
ルートのフルURLに対してGET
リクエストが送出され、処理が200 OK
で完了した後のブラウザのスクリーンショットは以下のようになります。
「Log in」というリンクが内容のヘッダー部に表示されていますね。
2. cookiesの内容を調べてみて、ログアウト後にはsessionが正常に削除されていることを確認してみましょう。
上記スクリーンショットは、Firefoxのストレージインスペクターによるログイン中のcookiesの内容です。
続いてのスクリーンショットは、Firefoxのストレージインスペクターによるログアウト後のcookiesの内容です。cookiesの内容は変化していますが、sessionが削除されているかどうかは正直わかりません。一方で、ログイン中には表示されなかった「パース済みの値」というボックスが表示されるようになりました。