module HaveAPI::Hooks
All registered hooks and connected endpoints are stored in this module.
It supports connecting to both class and instance level hooks. Instance level hooks inherit all class registered hooks, but it is possible to connect to a specific instance and not for all instances of a class.
Hook definition contains additional information for as a documentation: description, context, arguments, return value.
Every hook can have multiple listeners. They are invoked in the order of registration. Instance-level listeners first, then class-level. Hooks
are chained using the block’s first argument and return value. The first block to be executed gets the initial value, may make changes and returns it. The next block gets the return value of the previous block as its first argument, may make changes and returns it. Return value of the last block is returned to the caller of the hook.
Usage¶ ↑
Register hooks¶ ↑
class MyClass include Hookable has_hook :myhook, desc: 'Called when I want to', context: 'current', args: { a: 'integer', b: 'integer', c: 'integer', } end
Not that the additional information is just optional. A list of defined hooks and their description is a part of the reference documentation generated by yard.
Class level hooks¶ ↑
# Connect hook MyClass.connect_hook(:myhook) do |ret, a, b, c| # a = 1, b = 2, c = 3 puts "Class hook!" ret end # Call hooks MyClass.call_hooks(:myhook, args: [1, 2, 3])
Instance level hooks¶ ↑
# Create an instance of MyClass my = MyClass.new # Connect hook my.connect_hook(:myhook) do |ret, a, b, c| # a = 1, b = 2, c = 3 puts "Instance hook!" ret end # Call instance hooks my.call_instance_hooks_for(:myhook, args: [1, 2, 3]) # Call class hooks my.call_class_hooks_for(:myhook, args: [1, 2, 3]) # Call both instance and class hooks at once my.call_hooks_for(:myhook, args: [1, 2, 3])
Chaining¶ ↑
5.times do |i| MyClass.connect_hook(:myhook) do |ret, a, b, c| ret[:counter] += i ret end end p MyClass.call_hooks(:myhook, args: [1, 2, 3], initial: {counter: 0}) => {:counter=>5}
Constants
- INSTANCE_VARIABLE
Public Class Methods
Source
# File lib/haveapi/hooks.rb, line 145 def self.call_for( klass, name, where = nil, args: [], kwargs: {}, initial: {}, instance: nil ) classified = hook_classify(klass) all_hooks = if (instance.nil? && !classified.is_a?(Class)) || instance klass.instance_variable_get(INSTANCE_VARIABLE) else @hooks[classified] end catch(:stop) do return initial unless all_hooks return initial unless all_hooks[name] hooks = all_hooks[name][:listeners] return initial unless hooks hooks.each do |hook| ret = if where where.instance_exec(initial, *args, **kwargs, &hook) else hook.call(initial, *args, **kwargs) end initial.update(ret) if ret end initial end end
Call all blocks that are connected to hook in ‘klass` with `name`. klass
may be a class name or an object instance. If `where` is set, the blocks are executed in it with instance_exec. `args` is an array of arguments given to all blocks. The first argument to all block is always a return value from previous block or `initial`, which defaults to an empty hash.
Blocks are executed one by one in the order they were connected. Blocks must return a hash, that is then passed to the next block and the return value from the last block is returned to the caller.
A block may decide that no further blocks should be executed. In such a case it calls Hooks.stop
with the return value. It is then returned to the caller immediately.
@param klass [Class instance, instance] @param name [Symbol] hook name @param where [Class instance] class in whose context hooks are executed @param args [Array] an array of arguments passed to hooks @param kwargs [Hash] an array of arguments passed to hooks @param initial [Hash] initial return value @param instance [Boolean] call instance hooks or not; nil means auto-detect
Source
# File lib/haveapi/hooks.rb, line 106 def self.connect_hook(klass, name, &block) @hooks[hook_classify(klass)][name][:listeners] << block end
Connect class hook defined in ‘klass` with `name` to `block`. `klass` is a class name.
Source
# File lib/haveapi/hooks.rb, line 111 def self.connect_instance_hook(instance, name, &block) hooks = instance.instance_variable_get(INSTANCE_VARIABLE) unless hooks hooks = {} instance.instance_variable_set(INSTANCE_VARIABLE, hooks) end hooks[name] ||= { listeners: [] } hooks[name][:listeners] << block end
Connect instance hook from instance ‘klass` with `name` to `block`.
Source
# File lib/haveapi/hooks.rb, line 185 def self.hook_classify(klass) klass.is_a?(String) ? Object.const_get(klass) : klass end
Source
# File lib/haveapi/hooks.rb, line 91 def self.register_hook(klass, name, opts = {}) classified = hook_classify(klass) opts[:listeners] = [] @hooks ||= {} @hooks[classified] ||= {} @hooks[classified][name] = opts end
Register a hook defined by ‘klass` with `name`. @param klass [Class] an instance of Class, that is class name, not it’s instance @param opts [Hash] @option opts [String] :desc why this hook exists, when it’s called @option opts [String] :context the context in which given blocks are called @option opts [Hash] :args hash of block positional arguments @option opts [Hash] :kwargs hash of block keyword arguments @option opts [Hash] :initial - hash of initial values @option opts [Hash] :ret hash of return values