Module: OpenLoam::PendingActions

Defined in:
lib/open_loam/pending_actions.rb

Overview

Staging and reviewing gated mutations (see OpenLoam::PendingAction).

pending = OpenLoam::PendingActions.stage(
summary: "Raise the excavator's daily rate to 1100",
on: equipment, action: :update, changes: { daily_rate: 1100 }
)
pending.preview            # => { "daily_rate" => { "from" => 950, "to" => 1100 } }
pending.approve!(by: manager)   # role-gated; executes the change

stage NEVER touches the target — it only records the intent. OpenLoam does not intercept Active Record globally (that would be fragile and out of scope); this is the primitive a confirm-mode write path calls instead of saving.

Class Method Summary collapse

Class Method Details

.stage(summary:, on:, action:, changes: {}, idempotency_key: nil, actor: OpenLoam::Current.actor) ⇒ Object



16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
# File 'lib/open_loam/pending_actions.rb', line 16

def stage(summary:, on:, action:, changes: {}, idempotency_key: nil, actor: OpenLoam::Current.actor)
  target_type, target_id = resolve_target(on)
  changes = changes.transform_keys(&:to_s)
  key = idempotency_key || compute_key(target_type, target_id, action, changes)

  # The same proposal staged twice collapses to one row — but only a still
  # PENDING one. A rejected or executed proposal with this key may be
  # re-staged as a fresh pending row (the partial index allows the coexist).
  existing = OpenLoam::PendingAction.pending.find_by(idempotency_key: key)
  return existing if existing

  OpenLoam::PendingAction.create!(
    actor_id: actor&.id,
    action_type: action.to_s,
    target_type: target_type,
    target_id: target_id,
    changeset: changes,
    summary: summary,
    idempotency_key: key
  )
rescue ActiveRecord::RecordNotUnique
  # Lost a concurrent double-stage race — return the pending row that won.
  OpenLoam::PendingAction.pending.find_by!(idempotency_key: key)
end