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