Module: TypedEAV::Versioning
- Defined in:
- lib/typed_eav/versioning.rb,
lib/typed_eav/versioning/subscriber.rb
Overview
Phase 04 versioning namespace. Houses the Subscriber that writes TypedEAV::ValueVersion rows in response to Value lifecycle events dispatched by EventDispatcher.
## Architecture
-
TypedEAV::Versioning::Subscriber.call(value, change_type, context) is conditionally registered with EventDispatcher.register_internal_value_change at engine boot via ‘TypedEAV::Versioning.register_if_enabled`, which is invoked from the `config.after_initialize` block in lib/typed_eav/engine.rb. When TypedEAV.config.versioning is false (default), the helper returns early — no callable is added to the dispatcher chain. When true, the subscriber registers and runs FIRST in the value- change subscriber chain (slot 0 by `after_initialize` block declaration order — Phase 07 will declare its matview block LATER in the same engine to keep matview at slot ≥ 1).
-
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.versioning` master 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 per the EventDispatcher internal-vs-user error policy (03-CONTEXT.md §User-callback error policy). 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
Class Method Summary collapse
-
.register_if_enabled ⇒ Object
Conditionally register the Subscriber with EventDispatcher’s internal value-change subscriber chain.
Class Method Details
.register_if_enabled ⇒ Object
Conditionally register the Subscriber with EventDispatcher’s internal value-change subscriber chain. Called by the engine’s ‘config.after_initialize` block (lib/typed_eav/engine.rb).
Extracted into a class method (not inlined inside the after_initialize block) for testability: specs can call this against a freshly-cleared ‘EventDispatcher.value_change_internals` to exercise both branches (versioning on/off) in-process, without booting the engine. The slot-0 regression spec (plan 04-03 P03) and the zero-overhead verification spec (this plan, subscriber_spec engine-boot block) both rely on this seam.
Idempotent — safe to call multiple times. Calling twice with versioning on results in exactly ONE entry in ‘EventDispatcher.value_change_internals`. The idempotency check uses `Array#include?` against `Subscriber.method(:call)`; `Method#==` compares receiver+name (semantic equality), so two fresh `Subscriber.method(:call)` instances compare equal even though they are different Method objects. The engine block runs this exactly once per boot in production; the idempotency guard protects future code paths that might re-invoke for any reason.
When ‘TypedEAV.config.versioning` is false (default), this method is a no-op: zero callable in `value_change_internals`, zero per-write dispatch cost. That is the locked CONTEXT line 17 contract.
85 86 87 88 89 90 91 92 |
# File 'lib/typed_eav/versioning.rb', line 85 def self.register_if_enabled return unless TypedEAV.config.versioning method_ref = TypedEAV::Versioning::Subscriber.method(:call) return if TypedEAV::EventDispatcher.value_change_internals.include?(method_ref) TypedEAV::EventDispatcher.register_internal_value_change(method_ref) end |