Exercises

寫一個整合測試,測試密碼重設過期的分支,把以下程式碼的空格(FILL_IN)填上。這段程式碼使用了 response.body,用來回傳頁面的 HTML。有很多方法可以測試過期,下面的程式碼是檢查回傳的 HTML 中有沒有包含「expired」這個字。

test/integration/password_resets_test.rb

require 'test_helper'

class PasswordResetsTest < ActionDispatch::IntegrationTest

  def setup
    ActionMailer::Base.deliveries.clear
    @user = users(:michael)
  end
  .
  .
  .
  test "expired token" do
    get new_password_reset_path
    post password_resets_path, password_reset: { email: @user.email }

    @user = assigns(:user)
    @user.update_attribute(:reset_sent_at, 3.hours.ago)
    patch password_reset_path(@user.reset_token),
          email: @user.email,
          user: { password:              "foobar",
                  password_confirmation: "foobar" }
    assert_response :redirect
    follow_redirect!
    assert_match /FILL_IN/i, response.body
  end
end

目前所有的使用者都會在 /users 中顯示出來,並且可以透過 /users/:id 這樣的 URL 瀏覽,但應該是只有經過啟動的使用者才會顯示在頁面,這樣比較符合邏輯。完成下面的程式碼,來檢查有經過啟動的使用者才會被顯示的行為。下面的程式碼使用了 Active Record 的 where 方法,在下一章我們會學到更多關於這個方法的應用。

附加題:為 /users 和 /users/:id 編寫整合測試。

app/controllers/users_controller.rb

class UsersController < ApplicationController
  .
  .
  .
  def index
    @users = User.where(activated: FILL_IN).paginate(page: params[:page])
  end

  def show
    @user = User.find(params[:id])
    redirect_to root_url and return unless FILL_IN
  end
  .
  .
  .
end

注意這邊使用了 and 取代 &&,兩者的作用基本上一樣,但 && 的優先權更高,會和 root_url 綁定的太緊。我們可以把 redirect_to root_url 放在括號裡,但慣例上是使用 and


app/models/user.rb 中的 activatecreate_reset_digest 方法都調用了兩次 update_attribute 方法,每一次調用都要單獨執行一個 database transaction。完成下面的程式碼,使用 update_columns 方法來取代兩個 update_attribute 調用,這樣修改後只會觸及 database 一次。然後執行測試,確保測試通過。

app/models/user.rb

class User < ActiveRecord::Base
  attr_accessor :remember_token, :activation_token, :reset_token
  before_save   :downcase_email
  before_create :create_activation_digest
  .
  .
  .
  # Activates an account.
  def activate
    update_columns(activated: FILL_IN, activated_at: FILL_IN)
  end

  # Sends activation email.
  def send_activation_email
    UserMailer.account_activation(self).deliver_now
  end

  # Sets the password reset attributes.
  def create_reset_digest
    self.reset_token = User.new_token
    update_columns(reset_digest:  FILL_IN,
                   reset_sent_at: FILL_IN)
  end

  # Sends password reset email.
  def send_password_reset_email
    UserMailer.password_reset(self).deliver_now
  end

  private

    # Converts email to all lower-case.
    def downcase_email
      self.email = email.downcase
    end

    # Creates and assigns the activation token and digest.
    def create_activation_digest
      self.activation_token  = User.new_token
      self.activation_digest = User.digest(activation_token)
    end
end