module Olelo module ErrorHandler def self.included(base) base.extend(ClassMethods) end def handle_error(error) type = error.class while type self.class.error_handler[type].to_a.each {|prio,method| method.bind(self).call(error) } type = type.superclass end end module ClassMethods def error_handler @error_handler ||= {} end def error(error, priority = 99, &block) handler = (error_handler[error] ||= []) method = "ERROR #{error} #{handler.size}" define_method(method, &block) handler << [priority, instance_method(method)] handler.sort_by!(&:first) end end end # Include this module to add hook support to your class. # The class will be extended with {ClassMethods} which # provides the methods to register hooks. module Hooks def self.included(base) base.extend(ClassMethods) end # Execute block surrounded with hooks # # It calls the hooks that were registered by {ClassMethods#before} and {ClassMethods#after} and # returns an array consisting of the before hook results, # the block result and the after hook results. # # @param [Symbol] name of hook to call # @param *args Hook arguments # @return [Array] [*Before hook results, Block result, *After hook results] # @api public # def with_hooks(name, *args) result = [] result.push(*invoke_hook("BEFORE #{name}", *args)) result << yield ensure result.push(*invoke_hook("AFTER #{name}", *args)) end # Invoke hooks registered for this class # # The hooks can be registered using {ClassMethods#hook}. # # @param [Symbol] name of hook to call # @param *args Hook arguments # @return [Array] [Hook results] # @api public # def invoke_hook(name, *args) hooks = self.class.hooks[name.to_sym] raise "#{self.class} has no hook '#{name}'" if !hooks hooks.map {|prio,method| method.bind(self).(*args) } end # Extends class with hook functionality module ClassMethods # Hash of registered hooks # @api private # @return [Hash] of hooks def hooks @hooks ||= {} end def has_around_hooks(*names) names.each do |name| has_hooks "BEFORE #{name}", "AFTER #{name}" end end def has_hooks(*names) names.map(&:to_sym).each do |name| raise "#{self} already has hook '#{name}'" if hooks.include?(name) hooks[name] = [] end end # Register hook for class # # The hook will be invoked by {#invoke_hook}. Hooks with lower priority are called first. # # @param [Symbol, String] name of hook # @param [Integer] priority # @yield Hook block with arguments matching the hook invocation # @return [void] # @api public # def hook(name, priority = 99, &block) list = hooks[name.to_sym] raise "#{self} has no hook '#{name}'" if !list method = "HOOK #{name} #{list.size}" define_method(method, &block) list << [priority, instance_method(method)] list.sort_by!(&:first) end # Register before hook # # The hook will be invoked by {#with_hooks}. # # @param [Symbol, String] name of hook # @param [Integer] priority # @yield Hook block with arguments matching the hook invocation # @return [void] # @api public # def before(name, priority = 99, &block) hook("BEFORE #{name}", priority, &block) end # Register before hook # # The hook will be invoked by {#with_hooks}. # # @param [Symbol, String] name of hook # @param [Integer] priority # @yield Hook block with arguments matching the hook invocation # @return [void] # @api public # def after(name, priority = 99, &block) hook("AFTER #{name}", priority, &block) end end end end