在 ApplicationController 中注册不同类型的回调

Register a different type of callback in ApplicationController

我需要对控制器中的回调顺序进行更精细的控制。目前 Rails 只允许您使用 append|prepend_before|after_action,但如果您想添加一个带有专用回调的模块,这将非常糟糕。

我想了解 AbstractController::Callbacks 是如何工作的,我正在尝试注册一种新的回调类型,它将在特定时刻执行,利用 Rail 的控制器语法添加回调(only/except + 操作列表等)。

您可以将其视为自定义访问控制功能,但此问题与访问控制无关,请避免使用 Cancan 等 gem 进行回答轰炸。

class ApplicationController
  include xxx
  include MyModuleWithCallbacks
  include yyy
  ...
end

class MyController < ApplicationController
  prepend_before_action :something_before_my_callbacks
  my_callback_list :custom_callback, only: [:index, :show]
  before_action :something_after_my_callbacks
  # Goal : the order of above callbacks should NOT matter, my_callback does not depend on ActionController process_action callback list
end

module MyModuleWithCallbacks
  extend ActiveSupport::Concern
  extend AbstractController::Callbacks

  included do
    around_action :perform_if_condition

    def perform_if_condition
      run_callbacks :my_callback_list do 
        if my_callbacks_went_good?
          yield # And run the controller's before_callbacks
        else
          # log, render error page, etc.
        end
      end
    end

  # This is the hard part register the callback, I tried
  class_methods do
    define_method :my_callback_list do |*names, &blk|
      _insert_callbacks(names, blk) do |name, options|
        set_callback(:my_callback_list, :before, name, options)
      end
    end
  end

当前错误为

undefined method `_my_callbacks_list_callbacks' for PublicController:Class

我的灵感来自 AbstractController::Callbacks 的源代码,但我不确定我是否理解那里发生了什么 ^^"

我看到了一些赞成票,所以这是我目前的解决方案:

以一个非常轻量级的访问控制方法为例,my_callback的原名是access_control

# controllers/concerns/access_control.rb
    module AccessControl
      extend ActiveSupport::Concern
      class_methods do
        define_method :my_callback do |*names, &blk|
          _insert_callbacks(names, blk) do |name, options|
            set_callback(:my_callback, :before, name, options)
          end
        end
      end

      included do

        define_callbacks :my_callback

        def perform_if_access_granted
          run_callbacks :my_callback do
            if @access_denied and not @access_authorized and not god_mode?
              @request_authentication = true unless user_signed_in?
              render(
                file: File.join(Rails.root, 'app/views/errors/403.html'),
                status: 403,
                layout: 'error')
            else
              yield
            end
          end
        end

然后在包含该模块的其他控制器中(再次以访问控制为例)

# controllers/your_controller.rb
class YourController < SeekerController
  your_callback do
    set_entity
    allow_access_to_entity(@entity)
  end