to navigateEnterto select Escto close

    Keep application controller clean

    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

    Rails controllers come with concerns directory. All modules put in 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.

    Moving authorization functionality into a concern

    Here we are using extend ActiveSupport::Concern which allows us to use included and other features.

    Using concerns is another form of keeping code DRY.

    Let's try the same for authorization related code:

    1touch app/controllers/concerns/authorizable.rb

    Open app/controllers/concerns/authorizable.rb and add following code:

    1module Authorizable
    2  extend ActiveSupport::Concern
    4  included do
    5    rescue_from Pundit::NotAuthorizedError, with: :handle_authorization_error
    6    include Pundit
    7  end
    9  def handle_authorization_error
    10     render status: :forbidden, json: { error: t('authorization.denied') }
    11  end

    As we can see all code related to authorization declaration and enforcement are together in one file.

    Let's also modify our TasksController to invoke verify_authorized and verify_policy_scoped methods after certain specific actions:

    1class TasksController < ApplicationController
    2  after_action :verify_authorized, except: :index
    3  after_action :verify_policy_scoped, only: :index
    4  before_action :authenticate_user_using_x_auth_token
    6  #------previous code -------
    7  end

    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

    In order 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):

    1class ApplicationController < ActionController::Base
    2  include Authenticable
    3  include Authorizable
    4  include ApiException
    5  include SetHoneybadgerContext
    6  include RedirectHttpToHttps
    7  include EnsureTermsOfServiceIsAccepted
    8  include EnsureUserOnboarded
    9  include DataLoader

    Now let's modify our current application_controller and include the above created Authorizable concern:

    1class ApplicationController < ActionController::Base
    2  # previous code if any
    3  include Authorizable
    4  # previous code if any

    After making the above change, we can remove unwanted rescue and handlers related to pundit from the application_controller since all those concerns now belong in Authorizable.

    By convention, we prefer to name concerns with able suffix. It suggest 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 a helper.

    Now let's commit these changes, where we moved pundit related functionalities into Authorizable concern:

    1git add -A
    2git commit -m "Moved pundit helpers to Authorizable concern"