ActiveSupport Concerns in Ruby on Rails (Modules for callbacks and class methods)

This tutorial explains how to use ActiveRecord Concerns in Ruby on Rails. I will create a concern for to use in controller where we want.

ActiveSupport Concerns in Ruby on Rails (Modules for callbacks and class methods)

A few days ago while I was creating custom user authentications with JWT in rails API, I was concerned that this was blotting my application controller.

While I was researching a refactoring solution, I found about ActiveSupport Concerns.

It is rails builtin functionality for dealing the class methods and callback refactoring.

Problem Statement

I had a methodology that included three methods for authentication in the application controller for decoding JWT and verifying user.

First Method

  # to decode incoming token and return it else return error
  def decoded_token
    header = request.headers['Authorization']
    header = header.split(' ').last if header
    if header
      begin
        @decoded_token ||= JsonWebToken.decode(header)
      rescue ActiveRecord::RecordNotFound => e
        return render json: { errors: [e.message] }, status: :unauthorized
      rescue JWT::DecodeError => e
        return render json: { errors: [e.message] }, status: :unauthorized
      end
    end
  end

Second Method

  # Called in before action whether user authenticated required or not
  # It uses decoded_token method above and validate_token sets the last used
  # flag in user session table. So whether authentication is not required
  # user last used session time is updated
  def current_user
    @current_user = nil
    if decoded_token
      data = decoded_token
      user = User.find_by_id(data[:user_id])
      if user && user.validate_token(data[:token])
        @current_user ||= user
      end
    end
  end

Third Method

  # this calls the current_user above and gives error if user not present
  # this method to be called before action to be authenticated
  def authenticate_user
    unless current_user
      return render status: :unauthorized, json: {errors: [I18n.t('errors.unauthorized')]}
    end
    if current_user.is_block?
      return render status: :unauthorized, json: {errors: [I18n.t('errors.blocked')]}
    end
  end

Solution

I created a file in app/controlles/concerns/authorize_request.rb which is a module extended by ActiveSupport::Concern, and now I have to write include AuthorizeRequest. By this method now I can access callbacks and class methods of the class which has this module included.

So, my final file app/controllers/concerns/authorize_request.rb becomes:

module AuthorizeRequest
  extend ActiveSupport::Concern
  require 'json_web_token'

  before_action :current_user

  def authenticate_user
    unless current_user
      return render status: :unauthorized, json: {errors: [I18n.t('errors.unauthorized')]}
    end
    if current_user.is_block?
      return render status: :unauthorized, json: {errors: [I18n.t('errors.blocked')]}
    end
  end

  def current_user
    @current_user = nil
    if decoded_token
      data = decoded_token
      user = User.find_by_id(data[:user_id])
      if user && user.validate_token(data[:token])
        @current_user ||= user
      end
    end
  end

  def decoded_token
    header = request.headers['Authorization']
    header = header.split(' ').last if header
    if header
      begin
        @decoded_token ||= JsonWebToken.decode(header)
      rescue ActiveRecord::RecordNotFound => e
        return render json: { errors: [e.message] }, status: :unauthorized
      rescue JWT::DecodeError => e
        return render json: { errors: [e.message] }, status: :unauthorized
      end
    end
  end
end

Now in app/controllers/application_controller.rb I write only one line:

include AuthorizeRequest

By this, application controller has all the code included of authorize request without blotting it. Application Controller also have now included before_action :current_user that will be called now before every action in controllers that are inherited by application controller. And now every controller inherited by application controller have access to method current_user and also authenticate_user which can be called before action that is to be authenticated.

Final Comments

There is a faction of people that say:

Don’t use concerns

Because concerns loses the object-oriented approach by using modules that could be used in any class without any proper object-oriented approach.

My opinion is that yes this procedure is like a hack to the OOP approach. But if the developer knows what he/she is doing, then there is no problem. Besides, concerns are optional to use in rails not required and mostly gems which are the main power of rails already use concerns.

So, why are you concerned?