Module: StandardLedger::Entry
- Extended by:
- ActiveSupport::Concern
- Defined in:
- lib/standard_ledger/entry.rb
Overview
Marks an ActiveRecord model as a ledger entry: an immutable, append-only row that may project onto one or more aggregate targets.
Including this concern installs:
- the `ledger_entry` class macro (declares immutability + idempotency)
- read-only behavior post-creation (when `immutable: true`, the default)
- idempotency-by-unique-index (when `idempotency_key:` is non-nil)
Projection registration happens via the separate ‘Projector` concern —the two are decoupled so that an entry can be marked immutable without also opting into projections, and vice versa.
Instance Method Summary collapse
-
#idempotent? ⇒ Boolean
Returns true when this row was returned from an idempotent ‘create!` rescue — i.e.
-
#readonly? ⇒ Boolean
AR consults ‘readonly?` from `save`/`update` paths; raising ReadOnlyRecord here matches the ActiveRecord contract for persisted immutable rows.
-
#standard_ledger_targets ⇒ Hash{Symbol => ActiveRecord::Base}
Returns the entry’s belongs_to targets keyed by association name.
Instance Method Details
#idempotent? ⇒ Boolean
Returns true when this row was returned from an idempotent ‘create!` rescue — i.e. an existing row matched the unique constraint and was returned instead of inserted.
186 187 188 |
# File 'lib/standard_ledger/entry.rb', line 186 def idempotent? !!@_standard_ledger_idempotent end |
#readonly? ⇒ Boolean
AR consults ‘readonly?` from `save`/`update` paths; raising ReadOnlyRecord here matches the ActiveRecord contract for persisted immutable rows. New, unpersisted instances stay writable so the initial INSERT can land.
194 195 196 197 198 |
# File 'lib/standard_ledger/entry.rb', line 194 def readonly? return super unless standard_ledger_immutable? !new_record? end |
#standard_ledger_targets ⇒ Hash{Symbol => ActiveRecord::Base}
Returns the entry’s belongs_to targets keyed by association name. Used by the ‘entry.created` notification payload and by `StandardLedger.post`’s telemetry. Skips polymorphic and missing associations so the payload only includes what’s actually present.
Performance trade-off: this fires from ‘after_commit`, where AR may have cleared the association cache. Each `public_send(reflection.name)` can therefore issue a SELECT to reload the cached target. For the typical 1–2 belongs_to entry, that’s negligible. If profiling on a high-cardinality entry shows this matters, capture targets earlier (e.g. in ‘before_create`) and stash them on the instance — deferred to a future PR. Notably, an inline-mode caller has already resolved these targets by the time `after_commit` runs, so the SELECTs would only happen for entries with belongs_to associations that are not registered as projection targets.
217 218 219 220 221 222 223 224 225 226 |
# File 'lib/standard_ledger/entry.rb', line 217 def standard_ledger_targets return {} unless self.class.respond_to?(:reflect_on_all_associations) self.class.reflect_on_all_associations(:belongs_to).each_with_object({}) do |reflection, memo| next if reflection.polymorphic? target = public_send(reflection.name) memo[reflection.name] = target unless target.nil? end end |