As a project adds more features, the application_controller.rb file tends to
gather more and more code and soon it becomes very big and very messy.
Take a look at this
application_controller.rb
before the refactoring was done.
As we can see many disjoint entities exist within that file. In the first look
itself we can see that it doesn't adhere to the
DRY principle.
One method has nothing to do with another method. All the after_action
declarations are physically too far from the implementation of the method. For
example method set_honeybadger_context is 67 lines apart from the
implementation of that method.
Using concerns to keep sanity
Concerns in Rails are like Ruby modules that extend the ActiveSupport::Concern
module. Rails controllers come with concerns directory. All modules that
reside inside concerns directory are automatically loaded by Rails. It's
created by Rails team so that we can put related stuff together as a concern in
that directory. So let's try to use it.
Using concerns is another form of keeping code DRY.
Unlike services, which are designed for specific business actions, concerns are used to share behaviors across models or controllers, etc. This key distinction guides whether to implement a concern or a service.
Moving functionality into a concern
Let's extract the helper methods for sending back a response for API requests
from ApplicationController class and move it inside the api_responders.rb
concern.
Let's try the same for authorization related code:
Run the following command to create the api_responders.rb concern:
Now add the following lines of code to the api_responders.rb file:
Similarly let us extract the exception rescuing and its corresponding handler
methods out of the ApplicationController to the api_exceptions.rb concern.
Run the following command to create the api_exceptions.rb concern:
Now add the following lines of code to the api_exceptions.rb file:
There are a couple of key points to note here:
-
Helper methods declared inside the ApiResponders module are used inside the
ApiExceptions module. We can say that the ApiExceptions module is
dependent on ApiResponders module but we do not need to include
ApiResponders inside ApiExceptions.
We can do so because, since ActiveSupport::Concern dependencies are
gracefully resolved.
-
We have used an included block inside the ApiExceptions module. The
included block is available because we are extending the
ActiveSupport::Concern module.
When a class includes a module, the code present inside the included block
will be executed within the scope of the including class.
In the above code, when a class includes the ApiExceptions class, the
rescue_from callbacks will be executed within the scope of that class.
Whenever an exception occurs inside that class or its child class, the
rescue_from callback will be called.
The included block usually contains code like callbacks and macros.
Let us now move the authentication functionality out of the
ApplicationController to the authenticable.rb concern.
Run the following command to create the authenticable.rb concern:
Open app/controllers/concerns/authenticable.rb and add following code:
Let's also modify our TasksController to invoke verify_authorized and
verify_policy_scoped methods after certain specific actions:
verify_authorized raises an error if pundit authorization has not been
performed in specified actions. That is why we invoke it as an after_action
hook. It is used to prevent the programmer from forgetting to call authorize
from specified action methods.
Like verify_authorized, Pundit also adds verify_policy_scoped to our
controller. It tracks and makes sure that policy_scope is used in the
specified actions. This is mostly useful for controller actions like index
which find collections with a scope and don't authorize individual instances.
Sanitized version
To make the application_controller even thinner and neater, we just need to
include the necessary concerns, rather than defining the functionality with the
controller.
For example, in a fully fledged application, once all the code is moved to
concerns, then the application_controller.rb would look something like this
(no need to add the following changes):
Now let's modify our current application_controller and include the concerns
we created in the previous section. Fully replace the contents of
application_controller.rb file like this:
The @current_user is assigned when the authenticate_user_using_x_auth_token
inside the Authenticable concern is invoked when the server receives an API
request.
By convention, we prefer to name concerns with able suffix. It suggests the
addition of ability.
An important point to keep in mind is that we need to use concerns only when
the logic has to be shared within multiple controllers or related files.
If the logic is only specific to a controller, then we can either write it in
the private section or move it inside a helper.
Now let's commit these changes, where we moved pundit related functionalities
into Authorizable concern: