Module: ActsAsTbackend::Mirror

Defined in:
lib/acts_as_tbackend/mirror.rb

Overview

Plain-Ruby record -> fact mirror, deliberately independent of ActiveSupport so it is unit-testable without Rails. The AR Extension delegates here; an app can also call mirror! directly from its own background job.

A "record" is any object that responds to #id, #attributes (a Hash), and ideally #updated_at (used as the deterministic id version — a retry re-sends the same id and collapses to an idempotent replay).

Class Method Summary collapse

Class Method Details

.build_fact(record:, store:, event_type:, only: nil, except: nil, tombstone: false, valid_time: nil) ⇒ Object



16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# File 'lib/acts_as_tbackend/mirror.rb', line 16

def build_fact(record:, store:, event_type:, only: nil, except: nil, tombstone: false, valid_time: nil)
  store = store.to_s
  record_id = record.id
  value = tombstone ? { "_tombstone" => true } : select_value(record, only: only, except: except)

  Fact.build(
    id: Fact.derive_id(store: store, record_id: record_id, event_type: event_type,
                       source_version: source_version(record)),
    store: store,
    key: "#{store}:#{record_id}",
    value: value,
    valid_time: valid_time || record_valid_time(record),
    causation: "#{store}:#{record_id}:#{event_type}",
    producer: ActsAsTbackend.config.producer
  )
end

.disabled_resultObject



71
72
73
# File 'lib/acts_as_tbackend/mirror.rb', line 71

def disabled_result
  { ok: true, status: "disabled", committed: nil, retryable: false, response: nil, error: nil }
end

.mirror!(record:, store:, event_type:, **opts) ⇒ Object

Build + idempotent bounded-safe write. Soft/non-fatal: returns the client's soft result and never raises for a down daemon unless config.strict.



35
36
37
38
39
40
# File 'lib/acts_as_tbackend/mirror.rb', line 35

def mirror!(record:, store:, event_type:, **opts)
  return disabled_result unless ActsAsTbackend.enabled?

  fact = build_fact(record: record, store: store, event_type: event_type, **opts)
  ActsAsTbackend.client.write_fact_once_safe(fact)
end

.record_valid_time(record) ⇒ Object



63
64
65
# File 'lib/acts_as_tbackend/mirror.rb', line 63

def record_valid_time(record)
  record.respond_to?(:valid_time) ? record.valid_time : nil
end

.select_value(record, only:, except:) ⇒ Object



42
43
44
45
46
47
48
49
50
51
# File 'lib/acts_as_tbackend/mirror.rb', line 42

def select_value(record, only:, except:)
  attrs = stringify(record.attributes)
  if only
    attrs.slice(*Array(only).map(&:to_s))
  elsif except
    attrs.except(*Array(except).map(&:to_s))
  else
    attrs
  end
end

.source_version(record) ⇒ Object

A persisted version stamp, stable across retries; falls back to wall-clock only when the record has none (then the id is best-effort, not retry-stable).



55
56
57
58
59
60
61
# File 'lib/acts_as_tbackend/mirror.rb', line 55

def source_version(record)
  if record.respond_to?(:updated_at) && record.updated_at
    record.updated_at
  else
    Time.now
  end
end

.stringify(hash) ⇒ Object



67
68
69
# File 'lib/acts_as_tbackend/mirror.rb', line 67

def stringify(hash)
  hash.each_with_object({}) { |(k, v), out| out[k.to_s] = v }
end