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.
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?