Exercises

app/models/user.rb 中,我們定義了產生 token 和 digest 的 class method:

# Returns the hash digest of the given string.
  def User.digest(string)
    cost = ActiveModel::SecurePassword.min_cost ? BCrypt::Engine::MIN_COST :
                                                  BCrypt::Engine.cost
    BCrypt::Password.create(string, cost: cost)
  end

  # Returns a random token.
  def User.new_token
    SecureRandom.urlsafe_base64
  end

這樣定義很明確,表示我們會使用 User.digestUser.new_token 調用。不過定義 class method 還有兩種常用的方法,可能會讓人有點困惑。

執行測試,確認下面兩種寫法都會通過測試:

app/models/user.rb

class User < ActiveRecord::Base
  .
  .
  .
  # Returns the hash digest of the given string.
  def self.digest(string)
    cost = ActiveModel::SecurePassword.min_cost ? BCrypt::Engine::MIN_COST :
                                                  BCrypt::Engine.cost
    BCrypt::Password.create(string, cost: cost)
  end

  # Returns a random token.
  def self.new_token
    SecureRandom.urlsafe_base64
  end
  .
  .
  .
end
class User < ActiveRecord::Base
  .
  .
  .
  class << self
    # Returns the hash digest of the given string.
    def digest(string)
      cost = ActiveModel::SecurePassword.min_cost ? BCrypt::Engine::MIN_COST :
                                                    BCrypt::Engine.cost
      BCrypt::Password.create(string, cost: cost)
    end

    # Returns a random token.
    def new_token
      SecureRandom.urlsafe_base64
    end
  end
  .
  .
  .

注意在上述程式碼中的 self 是代表 User class,在 User model 中其他的 self 是表示 user 實例物件。


之前提過,由於 APP 的設計方式,在 test/integration/users_login_test.rb 整合測試中,無法使用 remember_token 虛擬屬性。不過其實可以透過在測試中使用 assigns 方法來取得。

在測試中可以使用 assigns 方法來取得在 controller 裡定義的實例變數,做法是把實例變數的 symbol 形式傳給 assigns 方法。例如,如果在 create action 定義了 @user 變數,在測試中就可以透過 assigns(:user) 的形式取得這個實例變數。

目前在 Sessions controller 裡面的 create action 定義了一個普通的變數 user (非實例變數),如果我們把它改成實例變數,就可以測試 cookies 是否包含使用者的 remember token。

在下面兩個程式碼中,完成缺少的部分(?FILL_IN),進而能完善 remember me 的 checkbox 測試:

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

test/integration/users_login_test.rb

require 'test_helper'

class UsersLoginTest < ActionDispatch::IntegrationTest

  def setup
    @user = users(:michael)
  end
  .
  .
  .
  test "login with remembering" do
    log_in_as(@user, remember_me: '1')
    assert_equal FILL_IN, assigns(:user).FILL_IN
  end

  test "login without remembering" do
    log_in_as(@user, remember_me: '0')
    assert_nil cookies['remember_token']
  end
  .
  .
  .
end