Help us understand the problem. What is going on with this article?

Rails Tutorialの知識から【ポートフォリオ】を作って勉強する話 #10 リメンバーミー機能編

こんな人におすすめ

  • プログラミング初心者でポートフォリオの作り方が分からない
  • Rails Tutorialをやってみたが理解することが難しい

前回:#9 永続セッション, cookie編
次回:#10.5 RSpecでTutorialのテストを書き直す

今回の流れ

  1. 今回のゴールを把握する
  2. リメンバーミーのビューを作る
  3. リメンバーミーのコントローラーを作る
  4. テストを作る

※ この記事は、ポートフォリオを作る理由をweb系自社開発企業に転職するためとします。
※ 2020年4月6日、記事を大幅に更新しました。

今回のゴールを把握する

ゴールは、自動ログインを選択する、リメンバーミー機能を作ることです。

まずは、リメンバーミーのためのチェックボックスをビューで作ります。
続いて、Sessionsコントローラーを編集します。
最後に、チェックボックスと永続セッションのテストを作ります。
その際、current_userのテストも作ります。

リメンバーミーの完成には、#9を進める必要があります。
まだの方は、#9と合わせてご覧ください。

以上です。

リメンバーミーのビューを作る

リメンバーミーのためのチェックボックスをビューで作ります。
スコープをsession、チェックボックスをremember_meとしています。
よって、params[:session][:remember_me]の形で取得できます。

app/views/sessions/new.html.erb
<% provide(:title, "ログイン") %>
<div class="container form-container login-container">
  <div class="row">
    <div class="col">
      <div class="form-logo-img">
        <%= link_to image_tag('lantern_lantern_logo.png', width: 100), root_path, class: "logo-img" %>
      </div>
      <h1 class="form-title">ログイン</h1>
      <%= form_with(scope: :session, url: login_path, local: true) do |form| %>

        <div class="form-group">
          <%= form.email_field :email, class: 'form-control', placeholder: "メールアドレス" %>
        </div>

        <div class="form-group">
          <%= form.password_field :password, class: 'form-control', placeholder: "パスワード" %>
        </div>

        <div class="form-group form-check">
          <%= form.check_box :remember_me, class: 'form-check-input' %>
          <%= form.label :remember_me, '次から保存(ログイン省略)', class: 'form-check-label' %>
        </div>

        <div class="form-group">
          <%= form.submit "ログイン", class: "btn btn-info btn-lg form-submit" %>
        </div>
      <% end %>

      <p class="form-go-to-signup-or-login">新しくはじめる方は<%= link_to "こちら", signup_path %></p>
    </div>
  </div>
</div>
/app/assets/stylesheets/application.scss.erb
// 中略
.form-submit {
  width: 100%;
  // margin-top: 1rem; // 削除
}

lantern_lantern_checkbox.png

リメンバーミーのコントローラーを作る

Sessionsコントローラーを編集します。
params[:session][:remember_me]に保存される値は、オン→'1'、オフ→'0'です。
参考演算子を使って、場合分けします。

app/controllers/sessions_controller.rb
class SessionsController < ApplicationController
# 中略
  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] = 'メールアドレスかパスワードが正しくありません'
      render 'new'
    end
  end
# 中略

テストを作る

リメンバーミーのテストを作ります。
説明の都合上、紹介しなかった#9のテストも作ります。

ここでの手順は、以下の通りです。

  • リメンバーミーと永続セッションのテストを作る
  • current_userのテストを作る

リメンバーミーと永続セッションのテストを作る

チェックボックスと永続セッションのテストを作ります。
テスト前に、spec内でログインできるローカルメソッドを用意しています。
テストの内容は、以下の通りです。

  • 二度ログアウトできたとして、それでもエラーは起こらないか(#9参照)
  • チェックボックスがオンの時、トークンが作られているか
  • チェックボックスがオフの時、トークンが作られていないか
  • チェックボックスがオンの時、ログアウト後のログインでトークンが残っていないか
spec/requests/users_logins_spec.rb
require 'rails_helper'

RSpec.describe "UsersLogins", type: :request do
  include SessionsHelper

  let(:user) { create(:user) }

  # ログインのメソッド
  def post_valid_information(remember_me = 0)
    post login_path, params: {
      session: {
        email: user.email,
        password: user.password,
        remember_me: remember_me
      }
    }
  end

  it "does not log out twice" do
    get login_path
    post_valid_information(0)
    expect(is_logged_in?).to be_truthy
    follow_redirect!
    expect(request.fullpath).to eq '/users/1'
    delete logout_path
    expect(is_logged_in?).to be_falsey
    follow_redirect!
    expect(request.fullpath).to eq '/'
    delete logout_path
    follow_redirect!
    expect(request.fullpath).to eq '/'
  end

  it "succeeds remember_token because of check remember_me" do
    get login_path
    post_valid_information(1)
    expect(is_logged_in?).to be_truthy
    expect(cookies[:remember_token]).not_to be_nil
  end

  it "has no remember_token because of check remember_me" do
    get login_path
    post_valid_information(0)
    expect(is_logged_in?).to be_truthy
    expect(cookies[:remember_token]).to be_nil
  end

  it "has no remember_token when users logged out and logged in" do
    get login_path
    post_valid_information(1)
    expect(is_logged_in?).to be_truthy
    expect(cookies[:remember_token]).not_to be_empty
    delete logout_path
    expect(is_logged_in?).to be_falsey
    expect(cookies[:remember_token]).to be_empty
  end
  # 中略
end

post_valid_informationメソッドは、引数でチェックボックスを指定します。

マッチャであるbe_nilとbe_emptyを使い分ける理由は、以下の通りです。

  • be_nilを使う理由 → be_emptyはnilを判定しないから
  • be_emptyを使う理由 → forgetメソッドでcookiesをdeleteする際の中身は、空ではあるが、nilではないから

current_userのテストを作る

current_userのテストを作ります。
テストの内容は、以下の通りです。

  • 永続セッションでユーザーを特定できているか
  • 不適なトークンをremember_digestに代入した時、current_userはnilか
spec/helpers/sessions_helper_spec.rb
require 'rails_helper'

RSpec.describe SessionsHelper, type: :helper do

  let(:user) { create(:user) }

  describe "#current_user" do
    it "returns right user when session is nil" do
      remember(user)
      expect(current_user).to eq user
      expect(is_logged_in?).to be_truthy
    end

    it "returns nil when remember digest is wrong" do
      remember(user)
      user.update_attribute(:remember_digest, User.digest(User.new_token))
      expect(current_user).to be_nil
    end
  end
end

1つ目のテストの必要性について解説します。
理由は、以下のelsifがテストできていないためです。
わざと例外を発生させるraiseを挿入しても、テストが通ってしまいます。

app/helpers/sessions_helper.rb
# 中略
  def current_user
    if (user_id = session[:user_id])
      @current_user ||= User.find_by(id: user_id)
    elsif (user_id = cookies.signed[:user_id]) # この部分
      raise # 追加
      user = User.find_by(id: user_id)
      if user && user.authenticated?(cookies[:remember_token])
        log_in user
        @current_user = user
      end
    end
  end
# 中略

そのためのテストでした。
今回は以上です。

参考になりました↓
Rails Tutorial 9.3.1 [Remember me] ボックスをテストする


前回:#9 永続セッション, cookie編
次回:#10.5 RSpecでTutorialのテストを書き直す

aokyo17
rails tutorial → ポートフォリオing. 誰もが経験した初心びくびく20代1年目.. フォローはすぐ返したい厨。
https://komucha.com/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away