Railsチュートリアル 第9章

9.1 Remember me 機能

9.1.1

演習1

問題なく動作しました.

演習2

もともとリスト9.4の書き方をしていました. リスト9.5の書き方は省略します.

9.1.2

疑問1 current_userメソッドの定義

第8章では一時セッションにユーザーIDを保存していましたが, 本章ではユーザー情報を永続的に保存するために, クッキーを用いています. そのため, 第8章で定義したcurrent_userメソッドを修正しています. それがこちらです.

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

はじめはsessioncookiesの評価がどうしてこの順番なのかがわかりませんでした.

コードを見た通り, sessionが空かどうかをチェックし, 空であった場合はcookiesが空かどうかのチェックに移っています. この評価の順番が正当化されるということは, 「sessionは空だがcookiesは空ではない」という状況がありえることになります. それはブラウザを閉じたあとのことですね. sessionは短命なのでブラウザを閉じれは消えてしまいます. しかしcookiesは残ったままです. これでこの順番に評価をすることが意味を持つわけですね.

また後に実装するように, ユーザーはRemember me機能を使わないという選択をとれるようになります. したがってユーザーによってはcookiesには値がない場合もあるわけです. それならば必ず利用されるsessionをはじめに評価するほうが合理的ですね.

疑問2 どうしてログアウトできないのか

演習の直前に, テストスイートが失敗することが述べられています. その理由として, ユーザーがログアウトできないからだと書いてあります. 今までに定義したヘルパーメソッド等を思い出しながら, これについて詳しく見ていきましょう.

実際に統合テストを実行すると失敗します. 失敗した箇所を以下に示します.

# test/integration/users_login_test.rb
# login_with_valid_information_followed_by_logout
# ログアウトの処理以降のみを抜粋

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

以下テストで実行される流れを書いていきます.

logout_pathdeleteリクエストが飛びます. これによりSessionsControllerdestroyアクションが実行されます.

# SessionsController

def destroy
    log_out
    redirect_to root_url
end

ログアウト処理がなされ, root_urlにリダイレクトしていますね. このログアウト処理はなにをしているかというと, sessionからユーザーIDを消去し, インスタンス変数の@current_userを初期化しています.

# app/helpers/sessions_helper.rb

def log_out
    session.delete(:user_id)
    @current_user = nil
end

テストの続きへ進みます.

テストヘルパーメソッドのis_logged_in?によって, ログインした状態かどうかをチェックしています.

def is_logged_in?
    !session[:user_id].nil?
end

log_outメソッドによりsessionの内容は消去しているので, 当然このアサーションはクリアします. 次です.

destroyアクションの定義内に, リダイレクトするように書かれていますから, redirected_toアサーションもクリアします. 問題は次です.

さて問題はselectアサーションです. これが失敗する理由は本文にもあるようにログアウトできていないからです. つまりログインしてしまっているからです. ちゃんとsessionを消去したのに, 復活してしまっているんですね. その答えがヘッダー内の次の一行にあります.

# app/views/layouts/_header.html.erb

<% if logged_in? %>

ログインしているかどうかによって, ヘッダーの内容を変化させるためのif文です. ではヘルパーメソッドのlogged_in?の定義を見てみましょう.

# app/helpers/sessions_helper.rb

def logged_in?
    !current_user.nil?
end

current_usernilかどうかで, ログインしているかどうかを判断しています. さてcurrent_userメソッドはどんな定義でしたっけ...? 疑問1でも取り上げたように, 以下のような定義でしたね.

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

そろそろ核心に近づいてきました. current_userメソッドでは, sessionが空であった場合はcookiesの中身を見ます. ログアウトしてsession情報を消去しtも, cookiesの中身は残ったままですよね. ということはそのデータをもとにしてユーザーの検索が行われます. そしてauthenticated?が通った場合, log_inメソッドが実行されてしまいます. ここが, session情報の復活点でした. cookie情報が残った状態でヘッダーのレンダリングを行えば, 自然とログインしてしまうようになっていたのです.

演習1, 2

省略.

9.1.3

これでログアウトができますね.

演習1

省略.

9.1.4

細かいnilの扱いは難しいですね...

演習1, 2, 3

省略.

9.2 [Remenber me] チェックボックス

省略.

9.3 [Remember me] のテスト

9.3.1

演習1

sessions_controller.rbcreate内のuserインスタンス変数@userに変えます. するとassignsメソッドでインスタンス変数を参照できるようになるので, より詳細にテストを書くことができますね.

# test/integration/users_login_test.rb

test "login with remembering" do
    log_in_as(@user, remember_me: '1')
    assert_equal cookies['remember_token'] , assigns(:user).remember_token
end

9.3.2

省略.