Edited at

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


こんな人におすすめ


  • プログラミング初心者でポートフォリオの作り方が分からない

  • Rails Tutorialをやってみたが理解することが難しい

前回:#9 永続セッション, cookie編

次回:#10.5 RSpecでTutorialのテストを書き直す


こんなことが分かる


  • リメンバーミー機能の実装方法

一緒に勉強していこう:bow:

注意:リメンバーミー機能は前回(#9)の導入を済ませた上で機能します。


今回の流れ


  1. ログイン画面にリメンバーミーのチェックボックスを表示する

  2. リメンバーミー機能を実装する


チェックボックスを表示する

まずはログイン画面にチェックボックスを表示する。

クラス名をつける際、Bootstrap4と3では異なるので注意する。

スタイリングはいい感じだったのでほぼそのまま。


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

(header省略)


リメンバーミー機能を実装する

チェックボックスは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
# 中略

これでリメンバーミー機能の実装は完了。


リメンバーミー機能のテストを書く

Tutorial 9.3.1 [Remember me] ボックスをテストするでは、テスト用のログインメソッドを用意していた。

それを参考にしながらこちらでも用意しよう。

テストヘルパーにログインメソッドを用意すると可読性が下がるので、現状はローカルに定義する。

(必要になればヘルパーに移行します)

後はいつも通りテストを書く。


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 "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_empty
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


この部分に注目。

expect(cookies[:remember_token]).to be_nil

ここだけnilかどうかを確認している。

いやいや、普通こうしようと思うんだけど、

expect(cookies[:remember_token]).to be_empty

これだとテストが通りません。

なぜかというと、remember_tokenを使用していない以上まだ何も入っていません。

つまりnilになります。

空か確認するマッチャbe_emptyはnilを判定できません

よってここだけはbe_nilを使用します。

でもこれは違います。

it "has no remember_token when users logged out and logged in" do

# 中略
expect(cookies[:remember_token]).to be_empty
end

ここはforgetメソッドでcookiesをdeleteする形になっている。

これは中身を空にするだけなのでnilではありません!

よってbe_falsey, be_emptyで統一するのは難しい。

だからこのような書き方になったのでした。


テストから漏れているリメンバーミー機能

これで終わり、、と思いきやこのテストはcurrent_userを正確にテストできていない。

ということはユーザを記憶しても機能しない。

問題の箇所に例外を発生させるraiseを入れて確認してみよう。

# 中略

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
# 中略


bash

$ rails spec

41 examples, 0 failures


テストが通ってしまった。これだと検証できていない。

というわけで新たなテスト生成。


bash

$ rails g rspec:helper sessions


注意:config/application.rbで以下を設定した場合削除する必要があります


config/application.rb

# 中略

config.generators.test_framework = :rspec
config.generators.system_tests = false
config.generators.stylesheets = false
config.generators.javascripts = false
config.generators.helper = false
# config.generators.helper_specs = false
config.generators.view_specs = false


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


この状態でテストをすると失敗します。


bash

$ rails spec:helpers


というわけでraiseを外して再確認。


bash

$ rails spec:helpers

4 examples, 0 failures


これでグリーンになった!

テストって難しい。


追記

[2019年8月18日]

テストを追加しました。

前回:#9 永続セッション, cookie編

次回:#10.5 RSpecでTutorialのテストを書き直す