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'
Class Method Summary collapse
-
.call_for(klass, name, where = nil, args: [], initial: {}, instance: nil) ⇒ Object
Call all blocks that are connected to hook in
klass
withname
. -
.connect_hook(klass, name, &block) ⇒ Object
Connect class hook defined in
klass
withname
toblock
. -
.connect_instance_hook(instance, name, &block) ⇒ Object
Connect instance hook from instance
klass
withname
toblock
. - .hook_classify(klass) ⇒ Object
- .hooks ⇒ Object
-
.register_hook(klass, name, opts = {}) ⇒ Object
Register a hook defined by
klass
withname
. - .stop(ret) ⇒ Object
Class Method Details
.call_for(klass, name, where = nil, args: [], 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.
143 144 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 |
# File 'lib/haveapi/hooks.rb', line 143 def self.call_for( klass, name, where = nil, args: [], initial: {}, instance: nil ) classified = hook_classify(klass) if (instance.nil? && !classified.is_a?(Class)) || instance all_hooks = klass.instance_variable_get(INSTANCE_VARIABLE) else all_hooks = @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| if where ret = where.instance_exec(initial, *args, &hook) else ret = hook.call(initial, *args) 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.
105 106 107 |
# File 'lib/haveapi/hooks.rb', line 105 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
.
110 111 112 113 114 115 116 117 118 119 120 |
# File 'lib/haveapi/hooks.rb', line 110 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
181 182 183 |
# File 'lib/haveapi/hooks.rb', line 181 def self.hook_classify(klass) klass.is_a?(String) ? Object.const_get(klass) : klass end |
.hooks ⇒ Object
99 100 101 |
# File 'lib/haveapi/hooks.rb', line 99 def self.hooks @hooks end |
.register_hook(klass, name, opts = {}) ⇒ Object
Register a hook defined by klass
with name
. klass
is an instance of Class, that is class name, not it’s instance. opts
is a hash and can have following keys:
- desc - why this hook exists, when it's called
- context - the context in which given blocks are called
- args - hash of block arguments
- initial - hash of initial values
- ret - hash of return values
90 91 92 93 94 95 96 97 |
# File 'lib/haveapi/hooks.rb', line 90 def self.register_hook(klass, name, opts = {}) classified = hook_classify(klass) opts[:listeners] = [] @hooks ||= {} @hooks[classified] ||= {} @hooks[classified][name] = opts end |
.stop(ret) ⇒ Object
185 186 187 |
# File 'lib/haveapi/hooks.rb', line 185 def self.stop(ret) throw(:stop, ret) end |