Class: CMDx::Callbacks

Inherits:
Object
  • Object
show all
Defined in:
lib/cmdx/callbacks.rb

Overview

Registry of lifecycle callbacks invoked by Runtime. Callbacks can be method names (Symbols dispatched via task.send), blocks/Procs (instance_exec'd on the task), or arbitrary #call objects.

Each registration may carry :if / :unless gates (Symbol, Proc, or any #call-able). Gates are evaluated against the task before the callback is invoked; non-passing gates skip the callback silently.

Constant Summary collapse

EVENTS =

Callback event names Runtime dispatches.

Set[
  :before_validation,
  :before_execution,
  :around_execution,
  :after_execution,
  :on_complete,
  :on_interrupted,
  :on_success,
  :on_skipped,
  :on_failed,
  :on_ok,
  :on_ko
].freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initializeCallbacks

Returns a new instance of Callbacks.



30
31
32
# File 'lib/cmdx/callbacks.rb', line 30

def initialize
  @registry = {}
end

Instance Attribute Details

#registryObject (readonly)

Returns the value of attribute registry.



28
29
30
# File 'lib/cmdx/callbacks.rb', line 28

def registry
  @registry
end

Instance Method Details

#around(event, task, &body) { ... } ⇒ void

This method returns an undefined value.

Wraps block with every callback registered for event as a nested chain (outer-first by declaration order). Each callback receives a continuation it must invoke exactly once: Symbol callbacks get it as their block (use yield); Procs/blocks are instance_exec'd on the task with (task, continuation); arbitrary callables receive (task, continuation). Gates skip individual links silently while still running the body.

Parameters:

  • event (Symbol)
  • task (Task)
  • body (#call)

    inner continuation (Runtime lifecycle body)

Yields:

  • the innermost link — the lifecycle body to wrap

Raises:

  • (CallbackError)

    when a callback fails to invoke its continuation



156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
# File 'lib/cmdx/callbacks.rb', line 156

def around(event, task, &body)
  callbacks = registry[event]
  return yield if callbacks.nil? || callbacks.empty?

  callbacks.reverse_each.reduce(body) do |succ, (callable, options)|
    lambda do
      next succ.call unless Util.satisfied?(options[:if], options[:unless], task)

      called = false
      cont = lambda do
        called = true
        succ.call
      end

      invoke(callable, task, cont, &cont)

      called || raise(CallbackError, <<~MSG.chomp)
        #{event} callback did not invoke its continuation.
        See https://drexed.github.io/cmdx/callbacks/#around_execution-the-wrap-the-whole-thing-hook
      MSG
    end
  end.call
end

#countInteger

Returns total callbacks across all events.

Returns:

  • (Integer)

    total callbacks across all events



118
119
120
# File 'lib/cmdx/callbacks.rb', line 118

def count
  registry.each_value.sum(&:size)
end

#deregister(event, callable = nil) ⇒ Callbacks

Drops callbacks registered for event. With no callable, removes every callback for event. With a callable, removes only the entries whose callback matches callable by == (works for Symbol method names, classes/modules, and any callable held by reference). When the last entry for event is removed, the key itself is dropped.

Parameters:

  • event (Symbol)

    one of EVENTS

  • callable (Symbol, #call, nil) (defaults to: nil)

    optional specific callback to remove

Returns:

Raises:

  • (ArgumentError)

    when event is unknown



83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
# File 'lib/cmdx/callbacks.rb', line 83

def deregister(event, callable = nil)
  unless EVENTS.include?(event)
    raise ArgumentError, <<~MSG.chomp
      unknown callback event #{event.inspect}, must be one of #{EVENTS.to_a.inspect}.
      See https://drexed.github.io/cmdx/callbacks/#what-callbacks-exist
    MSG
  end

  if callable.nil?
    registry.delete(event)
  elsif (entries = registry[event])
    entries.reject! { |cb, _opts| cb == callable }
    registry.delete(event) if entries.empty?
  end

  self
end

#empty?Boolean

Returns:

  • (Boolean)


108
109
110
# File 'lib/cmdx/callbacks.rb', line 108

def empty?
  registry.empty?
end

#initialize_copy(source) ⇒ void

This method returns an undefined value.

Parameters:

  • source (Callbacks)

    registry to duplicate



36
37
38
# File 'lib/cmdx/callbacks.rb', line 36

def initialize_copy(source)
  @registry = source.registry.transform_values(&:dup)
end

#key?(event) ⇒ Boolean

Returns whether a callback is registered under name.

Parameters:

  • name (Symbol)

Returns:

  • (Boolean)

    whether a callback is registered under name



103
104
105
# File 'lib/cmdx/callbacks.rb', line 103

def key?(event)
  registry.key?(event)
end

#process(event, task) ⇒ void

This method returns an undefined value.

Fires each callback registered for event against task. Skips any callback whose :if/:unless gates fail.

Parameters:

  • event (Symbol)
  • task (Task)

Raises:

  • (ArgumentError)

    when a callback is neither a Symbol nor responds to #call



129
130
131
132
133
134
135
136
137
138
139
140
# File 'lib/cmdx/callbacks.rb', line 129

def process(event, task)
  return if empty?

  callbacks = registry[event]
  return if callbacks.nil? || callbacks.empty?

  callbacks.each do |callable, options|
    next unless Util.satisfied?(options[:if], options[:unless], task)

    invoke(callable, task)
  end
end

#register(event, callable = nil, **options, &block) { ... } ⇒ Callbacks

Adds a callback for event.

Parameters:

  • event (Symbol)

    one of EVENTS

  • callable (Symbol, #call, nil) (defaults to: nil)

    method name or callable; pass either this or a block

  • block (#call, nil)

    callback body when callable is omitted

  • options (Hash{Symbol => Object})

Options Hash (**options):

  • :if (Symbol, Proc, #call)

    gate that must evaluate truthy

  • :unless (Symbol, Proc, #call)

    gate that must evaluate falsy

Yields:

  • the callback body

Returns:

Raises:

  • (ArgumentError)

    when both callable and a block are given, when the callback type is invalid, or when event is unknown



52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
# File 'lib/cmdx/callbacks.rb', line 52

def register(event, callable = nil, **options, &block)
  callback = callable || block

  if callable && block
    raise ArgumentError, "callback: provide either a callable or a block, not both"
  elsif !callback.is_a?(Symbol) && !callback.respond_to?(:call)
    raise ArgumentError, <<~MSG.chomp
      callback must be a Symbol or respond to #call (got #{callback.class}).
      See https://drexed.github.io/cmdx/callbacks/#how-do-i-register-one
    MSG
  elsif !EVENTS.include?(event)
    raise ArgumentError, <<~MSG.chomp
      unknown callback event #{event.inspect}, must be one of #{EVENTS.to_a.inspect}.
      See https://drexed.github.io/cmdx/callbacks/#what-callbacks-exist
    MSG
  end

  (registry[event] ||= []) << [callback, options.freeze]
  self
end

#sizeInteger

Returns number of distinct events with callbacks.

Returns:

  • (Integer)

    number of distinct events with callbacks



113
114
115
# File 'lib/cmdx/callbacks.rb', line 113

def size
  registry.size
end