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



137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
# File 'lib/cmdx/callbacks.rb', line 137

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, "#{event} callback did not invoke its continuation")
    end
  end.call
end

#countInteger

Returns total callbacks across all events.

Returns:

  • (Integer)

    total callbacks across all events



101
102
103
# File 'lib/cmdx/callbacks.rb', line 101

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



77
78
79
80
81
82
83
84
85
86
87
88
# File 'lib/cmdx/callbacks.rb', line 77

def deregister(event, callable = nil)
  raise ArgumentError, "unknown event #{event.inspect}, must be one of #{EVENTS.join(', ')}" unless EVENTS.include?(event)

  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)


91
92
93
# File 'lib/cmdx/callbacks.rb', line 91

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

#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`



112
113
114
115
116
117
118
119
120
121
# File 'lib/cmdx/callbacks.rb', line 112

def process(event, task)
  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
# File 'lib/cmdx/callbacks.rb', line 52

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

  if callable && block
    raise ArgumentError, "provide either a callable or a block, not both"
  elsif !callback.is_a?(Symbol) && !callback.respond_to?(:call)
    raise ArgumentError, "callback must be a Symbol or respond to #call"
  elsif !EVENTS.include?(event)
    raise ArgumentError, "unknown event #{event.inspect}, must be one of #{EVENTS.join(', ')}"
  end

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

#sizeInteger

Returns number of distinct events with callbacks.

Returns:

  • (Integer)

    number of distinct events with callbacks



96
97
98
# File 'lib/cmdx/callbacks.rb', line 96

def size
  registry.size
end