lib/olelo/hooks.rb
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