I need more fine grain control on the order of callbacks in my controllers. Currently Rails only let you use append|prepend_before|after_action
, but this is just extremely bad if you want to add a module with its dedicated callbacks.
I am trying to understand how AbstractController::Callbacks work, and I'm trying to register a new type of callback, that would be executed at a specific moment, taking advantage of Rail's controllers syntax for adding a callback (only/except + list of actions, etc.).
You can think of it as a custom Access Control feature, but this question isn't about access control, please refrain answerbombing with gems like Cancan.
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
The current error is
undefined method `_my_callbacks_list_callbacks' for PublicController:Class
I am taking my inspiration from the source code of AbstractController::Callbacks but I'm not sure I understand what's going on there ^^"
I saw some upvotes so here is my current solution :
With the example of a very lightweight Access control method, the original name of my_callback
was 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
Then in your other controllers that include the module (again with Access control example)
# controllers/your_controller.rb
class YourController < SeekerController
your_callback do
set_entity
allow_access_to_entity(@entity)
end