dynsne
dynsne

Reputation: 341

Best way to perform error handling using modules in Rails?

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.

Upvotes: 3

Views: 2627

Answers (2)

Darkisa
Darkisa

Reputation: 2047

I know this is an old question but I was struggling with it but found the fix. To fix the issue of:

`undefined local variable or method `active_user' for Api::FooController` 

You need to include extend ActiveSupport::Concern e.g.

module Error
  module ErrorHandling
    extend ActiveSupport::Concern

    # rest of your code
  end
end

Upvotes: 0

ulferts
ulferts

Reputation: 2242

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.

Upvotes: 2

Related Questions