【Rails】Remember meチェックボックスと永続ログインのテスト【Rails Tutorial 9章まとめ】

##Remember meチェックボックス
永続ログインするか否かを選択できるチェックボックスを、ログイン画面に実装する。

###チェックボックス用フォーム
チェックボックスはユーザー情報入力用のフォームと同じ要領で作成できる。

```erb:app/views/sessions/new.html.erb
  <%= 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)メソッドを呼び出して永続ログインし、０であればforget(user)メソッドを呼び出して一時ログインするように、Sessionsコントローラのcreateアクションを変更する。

```app/controllers/sessions_controller.rb
  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文と同じ意味である。

```rb
条件式 ? 処理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メソッドでログインをテストできる。

```test/test_helper.rb
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チェックボックスのテスト
チェックボックスのオンオフに対応する２つのテストを書く。

```test/integration/users_login_test.rb
 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オブジェクトのトークンが一致するか検証していた。
この２点をテストする。
（ここではテスト忘れを防ぐテクニックが紹介されているが、今は割愛する。）

```app/helpers/sessions_helper.rb
  # 記憶トークン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ヘルパー用のテストを作成する。

```console
$ touch test/helpers/sessions_helper_test.rb
```

テストは以下のようになる。

```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を入れる処理があるので、それも確認しておく。

二つ目のテストは、テスト用ユーザーのトークンに異なるトークンを入れてみて、現在のユーザーが取得できるかを確認している。