Railsチュートリアル 第8章 基本的なログイン機構 - ログアウト

# ログアウト

## 前提条件

現在開発中のサンプルアプリケーションは、「ユーザーが明示的にログアウトするまではログイン状態を保つ」という仕様になっています。一方現状では、「ユーザーが明示的にログアウトする」という機能が実装されていません。

一方で、ログアウト機能の実装の前提となる以下の要素は既に実装済みです。

* ログイン済みユーザー用ページの内容ヘッダー部における、ログアウト用のリンク
* `logout_path`への`DELETE`リクエストに対する、`sessions#destroy`へのルーティング
* Sessionsコントローラにおける、`destroy`アクションそのものの定義

これから新たに実装が必要になるのは、「Sessionsコントローラにおける、`destroy`アクションの実際の動作」ということになります。

## `session.delete`メソッド

セッションから情報を削除するために用いるメソッドです。今回削除するのはユーザーIDなので、引数として`:user_id`シンボルを取ります。

```ruby:
session.delete(:user_id)
```

上記コードを実行すると、指定したユーザーIDに対するセッションの情報を`nil`にできます。

## Sessionsヘルパーに`log_out`メソッドを実装する

ログアウト機能も、ログインに関連する機能と同様、様々なビューやコントローラで使われる可能性がある機能です。そのため、ログアウト機能の実体はSessionsヘルパーに`log_out`メソッドとして実装していきます。

```diff:app/helpers/sessions_helper.rb
  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
```

## ログアウト機能に対するテストを実装する

```diff:
  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](https://railstutorial.jp/chapters/basic_login?version=5.1#code-user_logout_test)で定義した３つのステップを実行してみて、うまく動いているかどうか確認してみましょう。

ログイン済みで、ログインユーザーのプロフィールページが表示された状態から開始します。

<img width="1185" alt="スクリーンショット 2019-11-03 22.14.04.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/323778/c125017d-8964-2680-8624-846bcb7b3236.png">

「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`で完了した後のブラウザのスクリーンショットは以下のようになります。

<img width="1185" alt="スクリーンショット 2019-11-03 22.14.38.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/323778/5909ae8c-6116-6d32-8dea-60d636d94ba2.png">

「Log in」というリンクが内容のヘッダー部に表示されていますね。

## 2. cookiesの内容を調べてみて、ログアウト後にはsessionが正常に削除されていることを確認してみましょう。

<img width="301" alt="スクリーンショット 2019-11-04 17.14.18.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/323778/27aa35b6-3962-1dce-c42c-10fca8a985cd.png">

上記スクリーンショットは、Firefoxのストレージインスペクターによるログイン中のcookiesの内容です。

<img width="301" alt="スクリーンショット 2019-11-04 17.14.43.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/323778/70e235aa-d250-c96f-62ba-336fb580226f.png">

続いてのスクリーンショットは、Firefoxのストレージインスペクターによるログアウト後のcookiesの内容です。cookiesの内容は変化していますが、sessionが削除されているかどうかは正直わかりません。一方で、ログイン中には表示されなかった「パース済みの値」というボックスが表示されるようになりました。