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) do |payload| LittleGhost::Support::Callbacks.replace(payload.merge(debug: true)) end decision = callbacks.run(:prepare, {}) decision.value # => {debug: true}
Public Class Methods
Source
# File lib/little_ghost/support/callbacks.rb, line 50 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 45 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 58 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 54 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 64 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 91 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 71 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 106 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.