Learn Ruby on Rails Book

Keep application controller clean

As a project adds more features application_controller.rb 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 things are happening here. 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.

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: :authorization_error
6    include Pundit
7  end
9  def 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
3include 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.

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"