Module: StandardLedger::Projector

Extended by:
ActiveSupport::Concern
Defined in:
lib/standard_ledger/projector.rb

Overview

Adds the ‘projects_onto` DSL to an Entry class. Each `projects_onto` declaration registers a single (target_association, mode, projector) tuple; multi-target fan-out is two declarations.

Examples:

block form

class VoucherRecord < ApplicationRecord
  include StandardLedger::Entry
  include StandardLedger::Projector

  ledger_entry kind: :action, idempotency_key: :serial_no, scope: :organisation_id

  projects_onto :voucher_scheme, mode: :inline do
    on(:grant)    { |scheme, _| scheme.increment(:granted_vouchers_count) }
    on(:redeem)   { |scheme, _| scheme.increment(:redeemed_vouchers_count) }
    on(:consume)  { |scheme, _| scheme.increment(:consumed_vouchers_count) }
    on(:clawback) { |scheme, _| scheme.increment(:clawed_back_vouchers_count) }
  end
end

class form

projects_onto :order, mode: :async, via: Orders::FulfillableProjector

Defined Under Namespace

Classes: Definition, HandlerDsl, SqlDsl, TriggerDsl

Instance Method Summary collapse

Instance Method Details

#apply_projection!(definition) ⇒ Boolean

Apply a single projection definition to this entry. Resolves the target association, evaluates the optional ‘if:` guard, looks up the per-kind handler (or falls back to the projector class), and invokes it.

‘lock:` is interpreted by the mode strategies (`Modes::Inline`, …) — not here. The inline strategy needs the lock to span both the handler invocation and the coalesced `target.save!`, so it wraps an entire per-target group rather than a single `apply_projection!` call. See `Modes::Inline#call` for the lock-spans-save guarantee.

The mode strategies call this method; hosts typically do not call it directly.

Parameters:

  • definition (Definition)

    one of the entry class’s registered projections.

Returns:

  • (Boolean)

    ‘true` when the handler (or projector class) ran; `false` when the projection short-circuited (guard returned false, target was nil, or permissive mode found no matching handler).

Raises:



408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
# File 'lib/standard_ledger/projector.rb', line 408

def apply_projection!(definition)
  if definition.mode == :sql
    raise Error,
          "apply_projection! is not supported for mode: :sql; " \
          "the recompute SQL runs through `Modes::Sql#call` directly with no per-kind dispatch"
  end

  if definition.mode == :trigger
    raise Error,
          "apply_projection! is not supported for mode: :trigger; " \
          "the database trigger fires from the DB on INSERT, not from Ruby"
  end

  return false if definition.guard && !instance_exec(&definition.guard)

  target = public_send(definition.target_association)
  return false if target.nil?

  if definition.projector_class
    definition.projector_class.new.apply(target, self)
    return true
  end

  kind = resolve_kind!
  handler = definition.handlers[kind.to_sym]

  if handler.nil?
    if definition.permissive
      handler = definition.handlers[:_]
      return false if handler.nil?
    else
      raise UnhandledKind,
            "#{self.class.name} has no handler for kind=#{kind.inspect} on projection :#{definition.target_association}"
    end
  end

  handler.call(target, self)
  true
end