Requiring logged-in users

為了要實現為登入的使用者必須要先登入,才能瀏覽有權限的頁面,我們要在 Users controller 使用 before filter

before filter 透過 before_action 方法設定,指定在某個 action 執行之前調用一個方法。為了實現要求使用者先登入的限制,我們要定義一個 logged_in_user 方法,然後在 before_action 裡面調用這個方法:

app/controllers/users_controller.rb

class UsersController < ApplicationController
  before_action :logged_in_user, only: [:edit, :update]
  .
  .
  .
  private

    def user_params
      params.require(:user).permit(:name, :email, :password,
                                   :password_confirmation)
    end

    # Before filters

    # Confirms a logged-in user.
    def logged_in_user
      unless logged_in?
        flash[:danger] = "Please log in."
        redirect_to login_url
      end
    end
end

在預設中,before filter 會應用在 controller 裡所有的 action 上,所以這邊我們傳入了 :only 參數,限制 filter 只作用在 :edit:update action 上。

現在如果你嘗試瀏覽 /users/1/edit,會得到以下結果:

現在執行測試會發生錯誤,因為 editupdate action 現在需要使用者先登入,但相對應的測試中沒有已登入的使用者。

所以現在要來修改測試,在瀏覽 editupdate action 之前,要先登入使用者,我們可以使用之前定義的 log_in_as 輔助方法來實現:

test/integration/users_edit_test.rb

require 'test_helper'

class UsersEditTest < ActionDispatch::IntegrationTest

  def setup
    @user = users(:michael)
  end

  test "unsuccessful edit" do
    log_in_as(@user)
    get edit_user_path(@user)
    .
    .
    .
  end

  test "successful edit" do
    log_in_as(@user)
    get edit_user_path(@user)
    .
    .
    .
  end
end

現在測試應該會通過(Green):

$ bundle exec rake test

雖然測試通過了,但如果把剛剛的 before_action 註解掉,測試還是會通過,這可不妙:

app/controllers/users_controller.rb

class UsersController < ApplicationController
  # before_action :logged_in_user, only: [:edit, :update]
  .
  .
  .
end

註解後,測試應該不能通過,所以我們要編寫測試捕捉這個問題。

因為 before filter 是應用在每個指定的 action 上,所以要在 Users controller test 撰寫相對應的測試。

計畫是使用正確的請求方法瀏覽 editupdate action,然後檢查 flash 有出現,最後確認有把使用者導向登入頁面。查詢 rake routes 可以知道請求的方法分別是 GET 和 PATCH。

完整的測試程式碼如下:

test/controllers/users_controller_test.rb

require 'test_helper'

class UsersControllerTest < ActionController::TestCase

  def setup
    @user = users(:michael)
  end

  test "should get new" do
    get :new
    assert_response :success
  end

  test "should redirect edit when not logged in" do
    get :edit, id: @user
    assert_not flash.empty?
    assert_redirected_to login_url
  end

  test "should redirect update when not logged in" do
    patch :update, id: @user, user: { name: @user.name, email: @user.email }
    assert_not flash.empty?
    assert_redirected_to login_url
  end
end

注意 getpatch 的參數:

get :edit, id: @user

patch :update, id: @user, user: { name: @user.name, email: @user.email }

這邊使用 Rails 的慣例,設定 id: @user 時,Rails 會自動使用 @user.id。在 patch 方法中,還要指定一個 user hash,這樣路由才會正常運作。

現在執行測試會無法通過,這正是我們要的結果。

然後把註解拿掉:

app/controllers/users_controller.rb

class UsersController < ApplicationController
  before_action :logged_in_user, only: [:edit, :update]
  .
  .
  .
end

再執行測試,就會通過了(Green):

$ bundle exec rake test

如果不小心讓未授權的使用者瀏覽 edit action,現在測試能立即捕捉。