Class: StandardLedger::Modes::Sql
- Inherits:
-
Object
- Object
- StandardLedger::Modes::Sql
- Defined in:
- lib/standard_ledger/modes/sql.rb
Overview
‘:sql` mode: applies the projection by running a single recompute `UPDATE` against the target table, inside the entry’s ‘after_create` callback. The recompute SQL is supplied via the block-DSL’s ‘recompute “…”` clause; the `:target_id` placeholder is bound to the foreign-key value the entry holds for the target association.
Lower-overhead than ‘:inline` for projections expressible as `UPDATE target SET col = (SELECT aggregate(…) FROM entries WHERE …)` — there’s no Ruby-side handler invocation, no per-counter in-memory mutation, and no AR object load. Naturally rebuildable: ‘StandardLedger.rebuild!` runs the same statement against each target the log references, so the `after_create` and `rebuild!` paths share the same recompute SQL.
Like ‘:inline`, this fires from `after_create` (in the entry’s transaction), so failures roll the entry back alongside the projection. The notification payload’s ‘:target` field is `nil` for `:sql` mode — loading the target object would defeat the point of the mode (it’s meant to avoid Ruby-side reads). Subscribers get ‘entry`, the projection definition, and the timing.
Class Method Summary collapse
-
.install!(entry_class) ⇒ void
Install the ‘after_create` callback on `entry_class` exactly once.
Instance Method Summary collapse
-
#call(entry) ⇒ void
Apply every ‘:sql` projection registered on the entry’s class.
Class Method Details
.install!(entry_class) ⇒ void
This method returns an undefined value.
Install the ‘after_create` callback on `entry_class` exactly once. Subsequent calls (e.g. when a second `:sql` projection is added later in the class body) are no-ops — the same callback handles all `:sql` projections registered on the class.
35 36 37 38 39 40 41 42 43 44 45 46 47 |
# File 'lib/standard_ledger/modes/sql.rb', line 35 def self.install!(entry_class) return if entry_class.instance_variable_get(:@_standard_ledger_sql_installed) unless entry_class.respond_to?(:after_create) raise ArgumentError, "#{entry_class.name || entry_class.inspect} cannot use mode: :sql " \ "because it does not respond to `after_create`. `:sql` mode requires " \ "an ActiveRecord-backed entry class." end entry_class.after_create { StandardLedger::Modes::Sql.new.call(self) } entry_class.instance_variable_set(:@_standard_ledger_sql_installed, true) end |
Instance Method Details
#call(entry) ⇒ void
This method returns an undefined value.
Apply every ‘:sql` projection registered on the entry’s class. Called from the ‘after_create` callback installed by `.install!`.
For each definition: resolve the target’s foreign key from the entry, evaluate the optional ‘if:` guard, and run the recompute SQL with `:target_id` bound to the FK value.
A nil FK (target unset for this entry) is silently skipped — the entry simply doesn’t project onto a target this round. A guard returning false is also silently skipped.
Any exception raised by the SQL escapes — the entry’s transaction rolls back with the projection. The ‘<prefix>.projection.failed` notification fires before the re-raise so subscribers see the failure.
67 68 69 70 71 72 73 74 75 76 77 78 79 |
# File 'lib/standard_ledger/modes/sql.rb', line 67 def call(entry) definitions = sql_definitions_for(entry.class) return if definitions.empty? definitions.each do |definition| next if definition.guard && !entry.instance_exec(&definition.guard) target_id = resolve_target_id(entry, definition) next if target_id.nil? run_recompute_sql(entry, definition, target_id) end end |