Rails 7.1 adds the ability to raise errors on missing callback actions
Rails controllers have callbacks allowing you to run
code at specific request lifecycle points.
You can use the only
or
except
options to specify which actions the callback should run for.
class UsersController < ApplicationController
before_action :set_user, except: [:index, :new, :create]
after_action :notify_admins, only: [:create]
end
In previous versions of Rails,
the only
and
except
options in controller callbacks
did not check to see if the actions they referred to existed.
This meant you could make a typo in the action name
and
the callback would not run,
even though it was supposed to.
In the example above,
if there is a typo in the only
option of the after_action
callback,
the notify_admins
method will never be executed for the create
action.
The admins of your system would never receive any notifications regarding
new sign-ups on your platform.
class UsersController < ApplicationController
before_action :set_user, except: [:index, :new, :create]
after_action :notify_admins, only: [:crete] # typo crete => create
end
Consider another scenario, if you refactor your codebase and change the name of a controller action referenced in a callback. You could accidentally deploy a bug to production.
class UsersController < ApplicationController
before_action :authorize_user, only: [:dashboard]
# many lines of code of other actions
# method name changed from +dashboard+ to +admin_dashboard+
def admin_dashboard
end
end
In the above example, a normal user of your system could exploit the bug in your code to access your admin dashboard and hack critical data.
Before Rails 7.1
Before Rails 7.1, there were two ways to validate these issues.
-
The first way was to cover all the test scenarios for the controller. This would ensure that all the possible paths through the controller were tested, including those that involved the callbacks.
-
The second way was to write a script that checks the missing actions added in the
only/except
option. This script would scan the controller code for any callbacks that referenced actions that did not exist.
In Rails 7.1
When before_action :callback, only: :action_name
is declared in a controller
that doesn't respond to action_name
, it raises an exception at request time.
This is a safety measure to ensure that typos
or
forgetfulness don't prevent a crucial callback from being run.
This feature is turned on for new applications with Rails 7.1 by default.
The config is true
in the Rails config/environments/development.rb
and
config/environments/test.rb
files.
To turn off this behaviour,
you must set the config to false
in your
application configuration.
config.action_controller.raise_on_missing_callback_actions = false
For the below UsersController
,
there is a typo in the after_action :notify_admins
only
option.
When the application requests any action in the controller,
it raises the below error.
class UsersController < ApplicationController
before_action :set_user, except: [:index, :new, :create]
after_action :notify_admins, only: [:crete] # typo crete => create
end
# Request for UsersController#index action will raise the error
The crete action could not be found for the :notify_admins
callback on UsersController,
but it is listed in the controller's
:only option.
Raising for missing callback actions is a new default in Rails 7.1,
if you'd
like to turn this off you can delete the option from the environment configurations
or set `config.action_controller.raise_on_missing_callback_actions` to `false`.
Adding undefined action in the callback options is a bug that should be caught during development. The feature is helpful while refactoring the code or there are typos when coding.
When to avoid this feature?
If your application has shared controller concerns, you might have to turn off this feature.
module AdminNotification
extend ActiveSupport::Concern
included do
before_action :notify_admins, only: [:create, :destroy]
end
end
class UsersController < ApplicationController
include AdminNotification
def create
end
end
class Admins::UsersController < ApplicationController
include AdminNotification
def destroy
end
end
The AdminNotification
concern has a before_action
that is included
in the UsersController
and
Admins::UsersController
.
Requesting any action in the UsersController
will raise
an error for the missing destroy
action.
Similarly,
a missing create
action error gets raised when
the request is made for any action in the Admins::UsersController
.
Turning this feature off makes more sense in these kind of scenarios.
To know more about this feature, please refer to this PR.