Module: RivetCms::Hooks

Defined in:
lib/rivet_cms/hooks.rb

Overview

Lifecycle event registry: the extension seam host apps (and the Pro engine) attach through. Subscribers must not raise into the publish path; failures are logged and swallowed.

RivetCms::Hooks.on(:publish) { |revision| ... }

Registration is idempotent per key: re-registering with the same key replaces the previous handler instead of adding a duplicate. Anonymous handlers key on their own identity, which is stable when registration happens in an initializer (runs once per boot). Code that registers from a reloadable context (e.g. to_prepare) must pass an explicit key, or every reload adds another copy:

RivetCms::Hooks.on(:publish, key: :sitemap) { |revision| ... }

Subscribers run in registration order; there is no ordering API, so no subscriber may depend on running before or after another.

Extensions can define their own events before subscribing or firing:

RivetCms::Hooks.register_event(:unpublish)

Built-in events:

:publish - a document revision was published (fires after commit,
         receives the published snapshot revision)
:prune   - a superseded revision is about to be destroyed by retention
         (fires inside the publish transaction, before the delete, so
         a subscriber can archive it elsewhere first)
:audit   - an admin mutation happened (receives a RivetCms::AuditEvent;
         see RivetCms::Audit for the payload contract)

Constant Summary collapse

BUILT_IN_EVENTS =
%i[publish prune audit].freeze
MUTEX =
Mutex.new

Class Method Summary collapse

Class Method Details

.eventsObject



42
43
44
# File 'lib/rivet_cms/hooks.rb', line 42

def events
  mutex.synchronize { known_events.to_a }
end

.on(event, callable = nil, key: nil, &block) ⇒ Object

Raises:

  • (ArgumentError)


46
47
48
49
50
51
52
53
54
55
56
# File 'lib/rivet_cms/hooks.rb', line 46

def on(event, callable = nil, key: nil, &block)
  handler = callable || block
  raise ArgumentError, "handler required" if handler.nil?

  mutex.synchronize do
    raise ArgumentError, "unknown event: #{event} (register_event it first)" unless known_events.include?(event)

    registry[event][key || handler] = handler
  end
  handler
end

.register_event(event) ⇒ Object



37
38
39
40
# File 'lib/rivet_cms/hooks.rb', line 37

def register_event(event)
  mutex.synchronize { known_events << event.to_sym }
  event.to_sym
end

.reset!Object

Test-only: wipes ALL subscribers including the engine's boot-time registrations (initializers do not rerun). Pair with snapshot/restore.



84
85
86
87
88
89
# File 'lib/rivet_cms/hooks.rb', line 84

def reset!
  mutex.synchronize do
    @registry = nil
    @known_events = nil
  end
end

.restore(snapshot) ⇒ Object



73
74
75
76
77
78
79
80
# File 'lib/rivet_cms/hooks.rb', line 73

def restore(snapshot)
  return if snapshot.nil? # never mistake a failed snapshot for "no subscribers"

  mutex.synchronize do
    @registry = Hash.new { |hash, event| hash[event] = {} }
    snapshot&.each { |event, handlers| @registry[event] = handlers.dup }
  end
end

.run(event, *args) ⇒ Object



58
59
60
61
62
63
64
65
# File 'lib/rivet_cms/hooks.rb', line 58

def run(event, *args)
  handlers = mutex.synchronize { registry.key?(event) ? registry[event].values : [] }
  handlers.each do |handler|
    handler.call(*args)
  rescue => error
    Rails.logger&.error("[RivetCms] #{event} hook failed: #{error.class}: #{error.message}")
  end
end

.snapshotObject

Test-only: capture subscriptions so an example can register handlers and hand the registry back exactly as it found it.



69
70
71
# File 'lib/rivet_cms/hooks.rb', line 69

def snapshot
  mutex.synchronize { registry.transform_values(&:dup) }
end