I'm pretty new to Rails and back-end API developement so excuse me if I misuse a concept or so. Right now I'm attempting to refactor a large amount of conditional error handling code that is sprinkled around the code base and move towards using an explicit list of rescued exceptions that's mixed into the API controller by including it in as a module. This will allow me to attach custom, but arbitrary, codes to each exception caught, so long as we use the bang alternatives for active record methods, and the error handling code can live in one place. So far, for the error handling module, I have something like this:
# app/lib/error/error_handling.rb
module Error
module ErrorHandling
def self.included(klass)
klass.class_eval do
rescue_from ActiveRecord::RecordNotFound do |e|
respond(:record_not_found, 404, e.to_s)
end
rescue_from ActiveRecord::ActiveRecordError do |e|
respond(e.error, 422, e.to_s)
end
rescue_from ActiveController::ParameterMissing do |e|
response(:unprocessable_entitry, 422, e.to_s)
end
rescue_from ActiveModel::ValidationError do |e|
response(e.error, 422, e.to_s)
end
rescue_from CustomApiError do |e|
respond(e.error, e.status, e.message.to_s)
end
rescue_from CanCan::AccessDenied do
respond(:forbidden, 401, "current user isn't authorized for that")
end
rescue_from StandardError do |e|
respond(:standard_error, 500, e.to_s)
end
end
end
private
def respond(_error, _status, _message)
render "layouts/api/errors", status: _status
end
end
end
Where layouts/api/errors
is a view built using jbuilder.
In the ApiController
we have:
# app/controllers/api/api_controller.rb
module Api
class ApiController < ApplicationController
include Error::ErrorHandling
attr_reader :active_user
layout "api/application"
before_action :authenticate_by_token!
before_action :set_uuid_header
respond_to :json
protect_from_forgery with: :null_session
skip_before_action :verify_authenticity_token, if: :json_request?
private
...
end
Unfortunately this doesn't seem to work. Running tests shows that the private methods are not being loaded at all and are considered undefined!
To be more specific, here are the errors emitted:
uninitialized constant Error::ErrorHandling::ActiveController
and
undefined local variable or method `active_user' for Api::FooController
Where active_user
is an attribute that is set inside of an instance variable by a method named set_active_user
. Which is obviously not being called.
However the ErrorHandling
module is being evaluated. How could this be? Am I namespacing incorrectly or something?
Thanks for reading.
The answer is broken down into two parts as I believe that there are two separate problems.
unitinalized constant error
The error
uninitialized constant Error::ErrorHandling::ActiveController
can be fixed by changing this
rescue_from ActiveController::ParameterMissing do |e|
response(:unprocessable_entitry, 422, e.to_s)
end
to this:
rescue_from ::ActiveController::ParameterMissing do |e|
response(:unprocessable_entitry, 422, e.to_s)
end
(adding ::
in front of the ActiveController
constant)
Constant lookup in ruby takes lexical nesting into account. As you reference the Constant within
module Error
module ErrorHandling
...
end
end
ruby will try to find the constant within this namespace if the constant is undefined before. Prepending ::
will tell ruby to ignore the nesting on constant lookup.
undefined local method
The error
undefined local variable or method `active_user' for Api::FooController
is raised because some code is calling the instance method active_user
on the class Api::FooController
where it is not defined.