Module: HaveAPI::Hooks
- Defined in:
- lib/haveapi/hooks.rb
Overview
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}
Constant Summary collapse
- INSTANCE_VARIABLE =
'@_haveapi_hooks'.freeze
Class Method Summary collapse
-
.call_for(klass, name, where = nil, args: [], kwargs: {}, initial: {}, instance: nil) ⇒ Object
Call all blocks that are connected to hook in ‘klass` with `name`.
-
.connect_hook(klass, name, &block) ⇒ Object
Connect class hook defined in ‘klass` with `name` to `block`.
-
.connect_instance_hook(instance, name, &block) ⇒ Object
Connect instance hook from instance ‘klass` with `name` to `block`.
- .hook_classify(klass) ⇒ Object
- .hooks ⇒ Object
-
.register_hook(klass, name, opts = {}) ⇒ Object
Register a hook defined by ‘klass` with `name`.
- .stop(ret) ⇒ Object
Class Method Details
.call_for(klass, name, where = nil, args: [], kwargs: {}, initial: {}, instance: nil) ⇒ Object
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.
145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 |
# 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 |
.connect_hook(klass, name, &block) ⇒ Object
Connect class hook defined in ‘klass` with `name` to `block`. `klass` is a class name.
106 107 108 |
# File 'lib/haveapi/hooks.rb', line 106 def self.connect_hook(klass, name, &block) @hooks[hook_classify(klass)][name][:listeners] << block end |
.connect_instance_hook(instance, name, &block) ⇒ Object
Connect instance hook from instance ‘klass` with `name` to `block`.
111 112 113 114 115 116 117 118 119 120 121 |
# 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 |
.hook_classify(klass) ⇒ Object
185 186 187 |
# File 'lib/haveapi/hooks.rb', line 185 def self.hook_classify(klass) klass.is_a?(String) ? Object.const_get(klass) : klass end |
.hooks ⇒ Object
100 101 102 |
# File 'lib/haveapi/hooks.rb', line 100 def self.hooks @hooks end |
.register_hook(klass, name, opts = {}) ⇒ Object
Register a hook defined by ‘klass` with `name`.
91 92 93 94 95 96 97 98 |
# 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 |
.stop(ret) ⇒ Object
189 190 191 |
# File 'lib/haveapi/hooks.rb', line 189 def self.stop(ret) throw(:stop, ret) end |