Railsチュートリアル9章 クッキー(remember me)

クッキー

第9章で学んだクッキーについてまとめる。

テストコードは省略する。

 

まずはmigration追加

 

$ rails generate migration add_remember_digest_to_users remember_digest:string

class AddRememberDigestToUsers < ActiveRecord::Migration[6.0]
  def change
    add_column :users, :remember_digest, :string
  end
end
$ rails db:migrate



app/models/user.rb
class User < ApplicationRecord
  before_save { self.email = email.downcase }
  validates :name,  presence: true, length: { maximum: 50 }
  VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i
  validates :email, presence: true, length: { maximum: 255 },
                    format: { with: VALID_EMAIL_REGEX },
                    uniqueness: true
  has_secure_password
  validates :password, presence: true, length: { minimum: 6 }

  # 渡された文字列のハッシュ値を返す
  def User.digest(string)
    cost = ActiveModel::SecurePassword.min_cost ? BCrypt::Engine::MIN_COST :
                                                  BCrypt::Engine.cost
    BCrypt::Password.create(string, cost: cost)
  end

  # ランダムなトークンを返す
  def User.new_token
    SecureRandom.urlsafe_base64
  end
end
# これでランダムな文字列の組み合わせとしてトークンを作っている

# 永続セッションのためにユーザーをデータベースに記憶する def remember self.remember_token = User.new_token update_attribute(:remember_digest, User.digest(remember_token)) end
# 作成したremember_tokenをdigest化でハッシュにしてそれをremember_digestとして保存している。

# 渡されたトークンがダイジェストと一致したらtrueを返す
def authenticated?(remember_token) BCrypt::Password.new(remember_digest).is_password?(remember_token) end
# ハッシュ化したremember_digest(dbのカラムに保存されてる)と引数として渡されたremember_tokenが合致するか確かめる。
   BCryptを使うことでハッシュ後とハッシュ前でも比較できる。






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
      remember 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






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
#ここでクッキーに情報を入れている。 # 現在ログインしているユーザーを返す(いる場合) def current_user if session[:user_id] @current_user ||= User.find_by(id: session[:user_id]) end end # ユーザーがログインしていればtrue、その他ならfalseを返す def logged_in? !current_user.nil? end # 現在のユーザーをログアウトする def log_out session.delete(:user_id) @current_user = nil end end






current_user変更
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])
      user = User.find_by(id: user_id)
      if user && user.authenticated?(cookies[:remember_token])
        log_in user
        @current_user = user
      end
    end
  end
user_id = session[:user_id]はイコールではなく、代入。user_idはただの変数。
だから最初のifはsession[:user_id]があればでok,あったらuser_idに代入する。
なかったらelsif文にいく。
ここでauthencticated?を使ってクッキーにあるremember_tokenとdbのカラムに保存したrember_digestを比較してあげてる。

# ユーザーがログインしていればtrue、その他ならfalseを返す def logged_in? !current_user.nil? end # 現在のユーザーをログアウトする def log_out session.delete(:user_id) @current_user = nil end end





ユーザーを忘れる
app/models/user.rb
class User < ApplicationRecord
  attr_accessor :remember_token
  before_save { self.email = email.downcase }
  validates :name,  presence: true, length: { maximum: 50 }
  VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i
  validates :email, presence: true, length: { maximum: 255 },
                    format: { with: VALID_EMAIL_REGEX },
                    uniqueness: true
  has_secure_password
  validates :password, presence: true, length: { minimum: 6 }

  # 渡された文字列のハッシュ値を返す
  def User.digest(string)
    cost = ActiveModel::SecurePassword.min_cost ? BCrypt::Engine::MIN_COST :
                                                  BCrypt::Engine.cost
    BCrypt::Password.create(string, cost: cost)
  end

  # ランダムなトークンを返す
  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)
    BCrypt::Password.new(remember_digest).is_password?(remember_token)
  end

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






app/helpers/sessions_helper.rb

module SessionsHelper

  # 渡されたユーザーでログインする
  def log_in(user)
    session[:user_id] = user.id
  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

app/controllers/sessions_controller.rb
class SessionsController < ApplicationController . . . def destroy log_out if logged_in? redirect_to root_url end end






app/models/user.rb

これはテストのせいで追加
class User < ApplicationRecord
  .
  .
  .
  # 渡されたトークンがダイジェストと一致したら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
end






[remember me]チェックボックス

app/views/sessions/new.html.erb
<% provide(:title, "Log in") %>
<h1>Log in</h1>

<div class="row">
  <div class="col-md-6 col-md-offset-3">
    <%= form_with(url: login_path, scope: :session, local: true) do |f| %>

      <%= f.label :email %>
      <%= f.email_field :email, class: 'form-control' %>

      <%= f.label :password %>
      <%= f.password_field :password, class: 'form-control' %>

      <%= 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 %>

    <p>New user? <%= link_to "Sign up now!", signup_path %></p>
  </div>
</div>






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)
チェックボックスにチェックを入れると1でremember,チェックを入れないと0でfoeget
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