class LittleGhost::Support::Callbacks
Callbacks lets extensions prepare, replace, or cancel framework work in a predictable order. A later callback sees any replacement made earlier in the chain.
A callback may return Callbacks.continue, Callbacks.cancel, or Callbacks.replace. Any other return value means continue. Replacements become the payload for later callbacks.
Every decision responds to continue?, cancel?, and replace?. A cancellation also exposes reason; a replacement exposes value. Extensions should depend on these methods rather than a decision’s concrete class.
callbacks = LittleGhost::Support::Callbacks.new(:prepare) callbacks.on(:prepare) { |payload| Callbacks.replace(payload.merge(debug: true)) } decision = callbacks.run(:prepare, {}) decision.value # => {debug: true}
Public Class Methods
Source
# File lib/little_ghost/support/callbacks.rb, line 48 def cancel(reason = nil) = Cancel.new(reason:)
Creates a decision whose cancel? predicate indicates that callback processing should stop. The returned value exposes the optional reason.
Source
# File lib/little_ghost/support/callbacks.rb, line 43 def continue = CONTINUE
Uses the shared decision whose continue? predicate indicates that callback processing should proceed.
Source
# File lib/little_ghost/support/callbacks.rb, line 56 def initialize(*names) @callbacks = names.to_h { |name| [name.to_sym, []] } @prepend_counts = names.to_h { |name| [name.to_sym, 0] } end
Starts an empty chain for the declared callback names.
Source
# File lib/little_ghost/support/callbacks.rb, line 52 def replace(value) = Replace.new(value:)
Creates a decision whose replace? predicate indicates that later callbacks should receive value.
Public Instance Methods
Source
# File lib/little_ghost/support/callbacks.rb, line 62 def initialize_copy(source) super @callbacks = source.instance_variable_get(:@callbacks).transform_values(&:dup) @prepend_counts = source.instance_variable_get(:@prepend_counts).dup end
Duplicates callback arrays so subclasses and instances can extend a copy.
Source
# File lib/little_ghost/support/callbacks.rb, line 89 def merge(other) merged = dup other.instance_variable_get(:@callbacks).each do |name, callbacks| prepend_count = other.instance_variable_get(:@prepend_counts).fetch(name) callbacks.first(prepend_count).reverse_each { |callback| merged.on(name, callback, prepend: true) } callbacks.drop(prepend_count).each { |callback| merged.on(name, callback) } end merged end
Combines this chain with other while preserving prepend ordering.
# File lib/little_ghost/support/callbacks.rb, line 69 def on(name, callable = nil, prepend: false, &block) callback = callable || block unless callback.respond_to?(:call) || callback.is_a?(String) || callback.is_a?(Symbol) raise ArgumentError, "A callback is required" end registered = @callbacks.fetch(name.to_sym) { raise ArgumentError, "Unknown callback: #{name}" } unless registered.include?(callback) if prepend registered.unshift(callback) @prepend_counts[name.to_sym] += 1 else registered << callback end end self end
Registers a callable, block, or receiver method name for name.
# File lib/little_ghost/support/callbacks.rb, line 104 def run(name, payload, context: nil, receiver: nil) current = payload @callbacks.fetch(name.to_sym) { raise ArgumentError, "Unknown callback: #{name}" }.each do |callback| decision = normalize(invoke(callback, current, context, receiver)) case decision when Continue next when Replace current = decision.value else return decision end end current.equal?(payload) ? self.class.continue : self.class.replace(current) end
Runs name until callbacks finish or one cancels the chain.
The returned decision responds to continue?, cancel?, and replace?. Cancellation decisions expose reason, while replacement decisions expose the final value.