require 'rescue_any/version'
module RescueAny
def self.included(base)
class << base
attr_accessor :rescue_statements
end
base.rescue_statements = {}
base.extend ClassMethods
end
module ClassMethods
# Rescue exceptions raised by an object's instance methods.
#
# rescue_any receives an exception class, and a trailing hash
# containing a on: option listing one or more method names to
# apply the rescue to, and a with: option containing a code block
# for handling the exception.
#
# The handling block can receive either zero or one argument, in the latter,
# it will be passed the rescued exception.
#
# class RemoteUser
# include RescueAny
#
# rescue_any RuntimeError, on: [:create, :update, :delete], with: lambda {|ex| puts 'ohoh: ' + ex.message }
#
# def create
# raise RuntimeError.new("raised by #create")
# end
#
# def update
# raise RuntimeError.new("raised by #update")
# end
#
# def delete
# raise RuntimeError.new("raised by #delete")
# end
# end
#
# Exceptions raised inside the handler are not caught and will propogate
# up to the client class.
def rescue_any(exception_klass, options = {})
unless exception_klass.is_a?(Class) && exception_klass <= Exception
raise ArgumentError.new('First argument must be an Exception or sub-class of Exception')
end
unless options[:with]
raise ArgumentError.new('Expected a handler, supply an option hash that as :with key as an argument')
end
method_block = options[:with]
method_names = [options.fetch(:on, [])].flatten
method_names.each do |method_name|
rescue_statements[method_name] ||= {}
rescue_statements[method_name][exception_klass] = method_block
end
end
def method_added(method_name)
if rescue_statements.keys.include?(method_name)
alias_method_name = "#{method_name}_without_rescue_any".to_sym
unless self.instance_methods.include?(alias_method_name)
self.send(:alias_method, alias_method_name, method_name)
self.send(:define_method, method_name) do
begin
self.send(alias_method_name)
rescue Exception => ex
exception_handled = false
exceptions_and_rescue_blocks = self.class.instance_eval { rescue_statements[method_name] }
exceptions_and_rescue_blocks.keys.each do |rescue_exception|
if ex.instance_of? rescue_exception
self.class.instance_eval do
rescue_handler = rescue_statements[method_name][rescue_exception]
rescue_handler.arity != 0 ? rescue_handler.call(ex) : rescue_handler.call
end
exception_handled = true
break
end
end
raise ex unless exception_handled
end
end
end
end
end
end
end