rescue_from handlers inside two different modules - ruby-on-rails

I try to create facade module, for module that do some generic exceptions handling, with additional logic.
For examle, first module:
module GenericErrorHandler
extend ActiveSupport::Concern
included do
rescue_from Sequel::NoMatchingRow do |e|
render json: {code: 404, error: e}
end
rescue_from StandardError do |e|
render json: {code: 500, error: e}
end
end
end
And second module with logging:
module ErrorLogger
extend ActiveSupport::Concern
include GenericErrorHandler
included do
rescue_from StandardError do |e|
puts "logged error #{e.to_s}"
raise
end
end
end
When I include ErrorLogger in class that raises StandardException, only handler from ErrorLogger was called. But I expect, that handler from GenericErrorHandler must be called too because of raise in ErrorLogger handler. Is this possible to achieve such behavior? (looks like rescue_from from other module rewrites handlers after module inclusion)

Do you consider using methods instead of blocks?
module GenericErrorHandler
extend ActiveSupport::Concern
def handle_standard_error(e)
render json: {code: 500, error: e}
end
included do
rescue_from Sequel::NoMatchingRow do |e|
render json: {code: 404, error: e}
end
rescue_from StandardError, with: :handle_standard_error
end
end
module ErrorLogger
include GenericErrorHandler
def handle_standard_error(e)
puts "logged error #{e.to_s}"
super
end
end

Related

Use rescue_from on model concern

I want to use rescue_from method on a model concern but neither exception error is rescued to the method i specified:
require "active_support/concern"
module CustomErrorHandler
extend ActiveSupport::Concern
include ActiveSupport::Rescuable
included do
rescue_from StandardError, with: :capture_error
end
private
def capture_error(e)
puts "Error catched!"
end
end
class FooClass
include CustomErrorHandler
def some_action
raise StandardError, 'Error'
end
end
err = FooClass.new
err.some_action
Traceback (most recent call last):
2: from (irb):28
1: from (irb):23:in `some_action'
StandardError (Error) # Didn't catch the error!
Anyone can help me on how can i solve this? Thanks!
This idea is fundamentially broken.
rescue_from is basically just syntactic sugar for:
class ThingsController < ApplicationController
around_action :wrap_in_a_rescue
def show
raise SomeKindOfException
end
private
def wrap_in_a_rescue
begin
yield
rescue SomeKindOfException
do_something_else
end
end
def do_something_else
render plain: 'Oh Noes!'
end
end
It only works because the controller declares a callback.
Its use makes sense in a controller as it rescues exceptions that occur in its callbacks as well as in the method itself and can be used with inheritiance to customize the error responses. It use does not make very much sense outside of that context.
While you could maybe fix this code by including ActiveSupport::Callbacks and declaring the requisite callbacks it is most likely a very overcomplicated solution to the original problem which can most likely be handled with a simple rescue or composition.

Wrapping Rails controller logic within if statements

I am trying to wrap my controller logic within the same conditional. Not sure how to achieve this as I am a bit new to ruby on rails. The pseudo code below is what I am trying to achieve
class Api::BaseController
include Api::AuthConcern if SAME_CONDITION
before_action -> { some_function() } if SAME_CONDITION
if SAME_CONDITION
rescue_from Api::AuthConcern::Unauthenticated do |e|
render status: 401, json: { error: e.error }
end
rescue_from Api::AuthConcern::PermissionDenied do |e|
render status: 403, json: { error: e.error }
end
end
end
You ain't gonna need it. Just use methods instead:
module Api
# Avoid adding "Concern" to your module names
# it provides absolutely no information about what the object does
module AuthConcern
extend ActiveSupport::Concern
included do
rescue_from Unauthenticated { |e| respond_to_unauthenticated(e.error) }
rescue_from PermissionDenied { |e| respond_to_permission_denied(e.error) }
end
def respond_to_unauthenticated(message)
render status: 401, json: { error: message }
end
def respond_to_permission_denied(message)
render status: 403, json: { error: message }
end
end
end
This lets any class that includes the module customize the behavior by simply overriding the method:
module Api
class FooController < BaseController
# ...
private
def respond_to_unauthenticated(error)
render plain: 'Oh noes, you broke it!'
end
end
end
If you need to add more logic to to how a module augments the class with you can use the macro pattern. This is simply a class method that adds methods or behavior to the class:
module Api
module AuthConcern
extend ActiveSupport::Concern
# ....
module ClassMethods
def authenticate!(**kwargs)
before_action :authenticate!, **kwargs
end
end
end
end
module Api
class FooController < BaseController
authenticate! except: :index
end
end
This pattern is found all over Ruby in everything from attr_accessor to Rails callbacks and assocations.

How to use rescue_from for an array of errors with splat operator

I want to catch an array of errors from my ActiveJob's perform method using rescue_from. I've been using the splat operator and rescue like this:
ERRORS = [
CustomErrorA,
CustomErrorB,
# ...
].freeze
def perform()
# implementation
rescue *ERRORS => e
handle_error(e)
end
And I would like to do something like:
rescue_from(*ERRORS, with: :handle_error)
# or alternatively
rescue_from *ERRORS do |e|
handle_error(e)
end
Is it possible to use the splat operator this way? Or do I have to keep the rescue block in order to catch all errors in the array? Is there any better way I'm not aware of?
rescue_from associates the handler with the exception class, but it still has to be called with rescue_with_handler, so the implementation would be:
require 'active_support/rescuable'
class Test
include ActiveSupport::Rescuable
class CustomErrorA < StandardError; end
class CustomErrorB < StandardError; end
ERRORS = [CustomErrorA, CustomErrorB]
rescue_from *ERRORS, with: :handle_error
def handle_error
puts "something is borked"
end
def rescue_test
raise Test::CustomErrorA
rescue *ERRORS => e
rescue_with_handler e
end
end
tt = Test.new
tt.rescue_test
It works, but it doesn't really buy you anything over what you are already using.

Global error handling and the order of included rescue_from

For my program to work as expected, the order of rescue_from is not intuitive. I'm wondering why this is or where I'm going wrong?
I was trying this error handling solution.
https://medium.com/rails-ember-beyond/error-handling-in-rails-the-modular-way-9afcddd2fe1b#.yvuf06281
My error handler was the same as in the github repo
module Error
module ErrorHandler
def self.included(clazz)
clazz.class_eval do
rescue_from ActiveRecord::RecordNotFound do |e|
respond(:record_not_found, 404, e.to_s)
end
rescue_from CustomError do |e|
respond(e.error, e.status, e.message.to_s)
end
rescue_from StandardError do |e|
respond(:standard_error, 500, e.to_s)
end
end
end
This results in my errors always being caught in the StandardError block, skipping the ActiveRecord::RecordNotFound and Custom error blocks.
However, if I switch the order (StandardError higher in the execution) it properly catches the other types of errors.
def self.included(clazz) #includes module as a class method
clazz.class_eval do
rescue_from StandardError do |e|
respond(:standard_error, 500, e.to_s)
end
rescue_from ActiveRecord::RecordNotFound do |e|
respond(:record_not_found, 404, e.to_s)
end
rescue_from CustomError do |e|
respond(e.error, e.status, e.message.to_s)
end
end
end
Why does having StandardError at the top work?
The last declared handler has the highest priority, so you should declare general handlers first (e.g. one for StandardError) and then specific ones.

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

Resources