Module: TypedEAV::Versioning
- Defined in:
- lib/typed_eav/versioning.rb,
lib/typed_eav/versioning/subscriber.rb
Overview
Transactional versioning namespace. Houses the Subscriber that writes TypedEAV::ValueVersion rows from Value lifecycle callbacks.
Architecture
-
TypedEAV::Versioning::Subscriber.call(value, change_type, context) is conditionally installed on Value's transactional callbacks at engine boot via
TypedEAV::Versioning.register_if_enabled. The version row therefore shares the source transaction. -
The subscriber is gated by TWO checks at call time (both must pass for a version row to be written):
1. value.field is non-nil (orphan guard — Value's field_id may have been NULLed by Phase 02's ON DELETE SET NULL cascade). 2. TypedEAV.registry.versioned?(value.entity_type) == true (per-entity opt-in via has_typed_eav versioned: true or include TypedEAV::Versioned).The
Config.versioningmaster switch is NOT re-checked inside the callable — when false, the subscriber is never registered in the first place. -
Errors raised by Subscriber.call propagate. Versioning corruption must be loud — silent failure leaves the audit log inconsistent with the live row.
Public API surface
The subscriber itself is gem-internal — apps do not call it directly. The public API is:
- `TypedEAV.config.versioning = true` — master switch.
- `has_typed_eav versioned: true` (or `include TypedEAV::Versioned`) —
per-entity opt-in.
- `TypedEAV.config.actor_resolver = -> { ... }` — actor identification.
- `TypedEAV.with_context(actor: ..., source: ...) { ... }` — request-
scoped audit context.
- `Value#history` and `Value#revert_to(version)` (plan 04-03).
Defined Under Namespace
Modules: Subscriber
Constant Summary collapse
- CALLBACKS =
{ create: %i[_write_version_create after].freeze, update: %i[_write_version_update after].freeze, destroy: %i[_write_version_destroy before].freeze, }.freeze
Class Method Summary collapse
- .atomic_callbacks_installed? ⇒ Boolean
-
.register_if_enabled ⇒ Object
Conditionally install the Subscriber on Value's transactional callbacks.
Class Method Details
.atomic_callbacks_installed? ⇒ Boolean
104 105 106 107 108 109 110 |
# File 'lib/typed_eav/versioning.rb', line 104 def self.atomic_callbacks_installed? CALLBACKS.all? do |event, (filter, kind)| TypedEAV::Value.send(:get_callbacks, event).to_a.any? do |callback| callback.filter == filter && callback.kind == kind end end end |
.register_if_enabled ⇒ Object
Conditionally install the Subscriber on Value's transactional callbacks.
Called by the engine's config.after_initialize block.
Extracted into a class method (not inlined inside the after_initialize block) for testability: specs can call this seam in-process without booting a second Rails application.
Idempotent — safe to call multiple times. Pool validation happens before any callback is installed, so a multi-database misconfiguration fails closed without partial activation.
When TypedEAV.config.versioning is false (default), this method is
a no-op: no callback is installed and the disabled path adds no
per-write predicate or dispatcher work.
71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 |
# File 'lib/typed_eav/versioning.rb', line 71 def self.register_if_enabled return unless TypedEAV.config.versioning value_pool = TypedEAV::Value.connection_pool version_pool = TypedEAV::ValueVersion.connection_pool unless value_pool.equal?(version_pool) raise ArgumentError, "TypedEAV versioning requires Value and ValueVersion to share a connection pool" end callback_chains = callback_chains_for_installation callback_chains.each do |event, (filter, kind, chain)| next if chain.any? { |callback| callback.filter == filter && callback.kind == kind } TypedEAV::Value.set_callback(event, kind, filter, prepend: true) end end |