Rails 使用者驗證:Devise Gem Customize Controllers
Table of Contents
我們 Rails 使用者驗證:Devise Gem Getting Start 一文中使用 Devise gem 在 Rails 專案中快速實現最基礎的使用者驗證功能。本文進一步對其路由與功能進行客製化。
簡介 ¶
我們可以透過 Devise 的 Generator 來產生 Devise 使用的 Controller,並依照我們的需求來修改功能的行為。
本文我們會使用 demo-devise Rails 專案,從 Rails 使用者驗證:Devise Gem Customize Views 一文的進度開始修改。
環境 ¶
本文使用的環境:
- macOS
 - Ruby 3.2.2
 - Rails 7.1.2
 
取得專案程式碼:
git clone https://github.com/timfanda35/demo-devise.git
cd demo-devise
git checkout customize_views
產生 Controller 檔案 ¶
以下指令可以產生 Devise 提供的所有 Controllers,我們需要指定 SCOPE 也就是驗證 Model。在 demo-devise 中我們使用的是慣例的 users:
bin/rails generate devise:controllers users

在訊息中可以看到,我們將需要修改 config/routes.rb,應用程式運行的時候才能將請求送往我們客製化後的 Controller 進行處理。
如果只需要客製化部分 Controllers,可以用 -c 指定,多個可以用空白分隔,例如:
bin/rails generate devise:controllers users -c sessions passwords
可以選擇的功能有:
confirmations:驗證信箱passwords:忘記密碼registrations:註冊/更新sessions:登入unlocks:帳號解鎖omniauth_callbacks:OmniAuth Callback
一但產生 Controller 檔案後,官方文件 建議將產生的 Devise 頁面範本從專案目錄 app/views/devise 移到 app/views/users 下。這並不是一個必要的步驟,但會讓我們的專案結構更為一致。
修改路由 ¶
執行指令確認目前 SCOPE 為 users 的路由:
bin/rails routes --grep="users"

可以發現目前 URI 在 /users 底下的路徑,都會將請求送到 devise 底下的 Controllers,而不是 users 底下的 Controllers。所以我們才需要依照提示修改 config/routes 修改路由將指定路徑的請求送到我們客製化的 Controller。
我們可以啟動 Rails Server 來觀察一下目前請求所使用的 Controller
bin/rails server
用瀏覽器開啟網址 http://localhost:3000/,點擊 Sign In,確認一下 Log:

可以發現送往 /users/sign_in 的請求是由 Devise::SessionsController 處理。
讓我們修改 config/routes.rb,改成由上面步驟產生的 Controller 來處理:
Rails.application.routes.draw do
  devise_for :users, controllers: {
    sessions: 'users/sessions'
  }
  resources :posts
  root "posts#index"
  get "up" => "rails/health#show", as: :rails_health_check
end
重新啟動 Rails Server
bin/rails server
用瀏覽器開啟網址 http://localhost:3000/,點擊 Sign In,確認一下 Log:

可以發現送往 /users/sign_in 的請求變成由 Users::SessionsController 來處理。
客製化 Controller ¶
我們在前述步驟產生的 Controller 都繼承了 Devise::Controller,而且檔案中已經註解好方法:

註解中的方法使用 super 去執行父物件的方法,我們可以查看 Devise::SessionsController 的原始碼 其中一段:
  # POST /resource/sign_in
  def create
    self.resource = warden.authenticate!(auth_options)
    set_flash_message!(:notice, :signed_in)
    sign_in(resource_name, resource)
    yield resource if block_given?
    respond_with resource, location: after_sign_in_path_for(resource)
  end
可以看到中間有一段
yield resource if block_given?
我們只需要傳入 block,就能夠在登入成功後接著進行我們自訂的額外行為,最後再繼續依照原本流程渲染回應頁面:
class Users::SessionsController < Devise::SessionsController
  def create
    super do |resource|
      # do something...
    end
  end
end
我們移除 Users::SessionsController 中的 create 方法註解,並進行修改,使其能夠在使用者成功登入後,印出登入時來源 IP 的 Log。
class Users::SessionsController < Devise::SessionsController
  def create
    super do |resource|
      Rails.logger.warn "\n\nUser ##{resource.id} has signed in from #{request.remote_ip}\n\n"
    end
  end
end
我們啟動 Rails Server,登入後確認 Log:

登入成功會顯示 User #1 has signed in from 127.0.0.1。

登入失敗則不會顯示。
總結 ¶
我們可以透過 Devise 提供的 Generator 產生可供客製化 Controller 檔案,在該 Controller 中我們將 block 傳入父物件的方法來在該方法的 Happy Path 中去執行我們所想要添加的額外行為。