コミュ障だから明日が僕らをよんだって返事もろくにしなかった

何かを創る人に憧れたからブログをはじめたんだと思うよ

社会のボトムズがRailsに手を出す #9

これまでのあらすじ

『人はなぜ自分だけのWebサービスを作りたいと思うのだろうか?そんなことを考えていたら "Railsチュートリアル" に手を出していた。こんなつもりではなかった。ただ興味本位で手を出しただけだ。』、冷たい取調室の中でそんなことを訴えるのであった。


ちゃんとあらすじを書かないのは過去の内容を知りたければ前回記事を読んでっていう願いとこんなバカなことやっている人間でもRailsチュートリアル進めることできるよっていう想いからです [要出典]。はい、前回はログイン機構を作りました。今回はそのアドバンスな内容やっていきます。

前回記事
inujini.hatenablog.com


みんな大好きハッテン回

そうしたわけで前回作ったログイン機能の拡張を今回やっていきます。ボーナス回ですね(?)。機能的には「このコンピュータにログイン情報を記録する」ってやつの実装です。

まずは何はともあれremember_digestを作るためにDB拡張していきます。

$ rails generate migration add_remember_digest_to_users remember_digest:string
$ rails db:migrate

そうしたらモデルにいろいろメソッドを書き加えていきます。

# app/models/user.rb
class User < ApplicationRecord
  attr_accessor :remember_token
  ### いろいろ略 ###
  # ランダムなトークンを返す
  def User.new_token
    SecureRandom.urlsafe_base64
  end

  # 永続セッションのためにユーザーをデータベースに記憶する
  def remember
    self.remember_token = User.new_token
    update_attribute(:remember_digest, User.digest(remember_token))
  end

  # 渡されたトークンがダイジェストと一致したらtrueを返す
  def authenticated?(remember_token)
    return false if remember_digest.nil?
    BCrypt::Password.new(remember_digest).is_password?(remember_token)
  end

  # ユーザーのログイン情報を破棄する
  def forget
    update_attribute(:remember_digest, nil)
  end 
enc

コメントついているから特に話すことがない。ノリ的にはremember_digestっていう長期間保持のために一時的(?)に扱うデータを作ったり破壊したりしてる感じ。


そんでモデルでの定義ができたら、コントローラ(とヘルパー)でやりたいことを記述していきます。

# app/controllers/sessions_controller.rb
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
      params[:session][:remember_me] == '1' ? remember(@user) : forget(@user)
      redirect_to @user
    else
      # エラーメッセージを作成する
      flash.now[:danger] = 'Invalid email/password combination'
      render 'new'
    end
  end

  def destroy
    log_out if logged_in?
    redirect_to root_url    
  end  
end
# app/helpers/sessions_helper.rb
module SessionsHelper
  # 渡されたユーザーでログインする
  def log_in(user)
    session[:user_id] = user.id
  end

  # ユーザーのセッションを永続的にする
  def remember(user)
    user.remember
    cookies.permanent.signed[:user_id] = user.id
    cookies.permanent[:remember_token] = user.remember_token
  end

  # 記憶トークン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])
      #raise     
      user = User.find_by(id: user_id)
      if user && user.authenticated?(cookies[:remember_token])
        log_in user
        @current_user = user
      end
    end
  end

  # ユーザーがログインしていればtrue、その他ならfalseを返す
  def logged_in?
    !current_user.nil?
  end

  # 永続的セッションを破棄する
  def forget(user)
    user.forget
    cookies.delete(:user_id)
    cookies.delete(:remember_token)
  end

  # 現在のユーザーをログアウトする
  def log_out
    forget(current_user)
    session.delete(:user_id)
    @current_user = nil
  end  
end

個人的な話なんですけど似たようなメソッド名が並ぶとコメントつけても流れを追わないと分かんなくなるんですけど、練度を上げると分かるようになるんですかね?それともアレですかね、分かりやすい命名に時間とるぐらいならコントローラぶくぶく太らせる僕の思考がひどすぎるからよくわかんないって結果になっているだけですかね…?まあいいや。これで情報を保持してくれるとのことです。

ガワの方はチェックボックスつくってremember_me投げるようにしてやればOKっぽいね。
f:id:andron:20190113221957p:plain



そんでテストはこんな感じ。

# test/helpers/sessions_helper_test.rb
require 'test_helper'
class SessionsHelperTest < ActionView::TestCase
  def setup
    @user = users(:michael)
    remember(@user)
  end

  test "セッションがnilのときはcurrent_userはuserを返す" do
    assert_equal @user, current_user
    assert is_logged_in?
  end

  test "セッションが正しくないときcurrent_userはnilを返す" do
    @user.update_attribute(:remember_digest, User.digest(User.new_token))
    assert_nil current_user
  end
end
# test/integration/users_login_test.rb
require 'test_helper'
class UsersLoginTest < ActionDispatch::IntegrationTest
  def setup
    @user = users(:michael)
  end

  ### いろいろ略  ###
  test "ログイン情報記憶してログイン" do
    log_in_as(@user, remember_me: '1')
    assert_equal cookies['remember_token'], assigns(:user).remember_token
  end

  test "ログイン情報記憶せずにログイン" do
    # クッキーを保存してログイン
    log_in_as(@user, remember_me: '1')
    delete logout_path
    # クッキーを削除してログイン
    log_in_as(@user, remember_me: '0')
    assert_empty cookies['remember_token']
  end  
end
# test/models/user_test.rb
require 'test_helper'

class UserTest < ActiveSupport::TestCase
  # 初期値
  def setup
    @user = User.new(name: "Example", email: "user@example.com", password: "foobar", password_confirmation: "foobar")
  end
  ### いろいろ略 ###
  test "ダイジェストが存在しない場合のauthenticated?" do
    assert_not @user.authenticated?('')
  end  
end

ここでは書かなかったけどassert_***は必須パラメータのあとにパラメータをつけると文字列メッセージになるらしい。ますますテストの日本語化が捗りますね。

参考
Rails テスティングガイド | Rails ガイド


第九章の感想

内容としてはそんな感じで、クッキー☆メソッドを使うことでページの状態を長期保持できるとのことです。いやーこれでハッテン的な機能が追加できるんですね感動ものですよ。大枠としてはそんな感じです。今回学びとして細かいところにちょろちょろあったのですが、なんかいろいろ細かすぎてここに書けない。すまぬ。まあ、今後学びを使うような機会があったらなんかネタ記事にして投稿します。


次回はユーザ情報の更新などをやっていくとのことです。2章で簡単に作った内容を10章で実際に作ってみて確認してみましょうって感じなのかな?

つづく……。