Class: ApprovalEngine::Approval
- Inherits:
-
ApplicationRecord
- Object
- ActiveRecord::Base
- ApplicationRecord
- ApprovalEngine::Approval
- Defined in:
- app/models/approval_engine/approval.rb
Overview
The aggregate root of one approval run: a host record + the event that spawned it, fanning out into one or more parallel tracks that gather per ‘approvals_required` (`:all` by default, like a layer). Progression methods run while the approval row is locked by the acting step, so they don’t relock.
Constant Summary collapse
- STATUSES =
%w[pending approved rejected quarantined cancelled].freeze
- TERMINAL_STATUSES =
%w[approved rejected quarantined cancelled].freeze
Instance Method Summary collapse
-
#cancel!(reason: nil) ⇒ Object
Withdraw an in-flight approval — the third terminal outcome beside approved and rejected, for when the thing being approved is voided or retracted.
-
#current_bottleneck ⇒ Object
The step this approval is currently waiting on the longest — i.e.
-
#gather! ⇒ Object
Re-evaluate after any track resolves: approve once enough tracks have, fail once the count is unreachable, else wait.
- #step ⇒ Object
- #terminal? ⇒ Boolean
-
#track ⇒ Object
Convenience readers for the common single-track case.
Instance Method Details
#cancel!(reason: nil) ⇒ Object
Withdraw an in-flight approval — the third terminal outcome beside approved and rejected, for when the thing being approved is voided or retracted. Cancels any still-open tracks/steps and fires ‘after_cancelled`. A no-op once terminal. Unlike the gather, this is a host entry point, so it takes its own lock.
79 80 81 82 83 84 85 86 87 88 89 90 |
# File 'app/models/approval_engine/approval.rb', line 79 def cancel!(reason: nil) return self if terminal? with_lock do return self if terminal? update!(status: "cancelled") cancel_remaining_tracks! emit_outbox("approval.cancelled", reason) end self end |
#current_bottleneck ⇒ Object
The step this approval is currently waiting on the longest — i.e. where it’s stuck right now. The oldest still-pending step across all tracks, or nil if nothing is pending. ‘step.waiting_for` gives the elapsed seconds; the host decides what counts as “late” and whether to nudge or escalate.
55 56 57 |
# File 'app/models/approval_engine/approval.rb', line 55 def current_bottleneck steps.pending.order(:activated_at).first end |
#gather! ⇒ Object
Re-evaluate after any track resolves: approve once enough tracks have, fail once the count is unreachable, else wait. A layer’s logic, over tracks.
61 62 63 64 65 66 67 68 69 70 71 72 |
# File 'app/models/approval_engine/approval.rb', line 61 def gather! return if terminal? case track_outcome when :met update!(status: "approved") cancel_remaining_tracks! emit_outbox("approval.approved") when :failed fail_gather!(reason: "required track approvals are no longer reachable") end end |
#step ⇒ Object
47 48 49 |
# File 'app/models/approval_engine/approval.rb', line 47 def step steps.sole end |
#terminal? ⇒ Boolean
34 35 36 |
# File 'app/models/approval_engine/approval.rb', line 34 def terminal? TERMINAL_STATUSES.include?(status) end |
#track ⇒ Object
Convenience readers for the common single-track case. An approval always has at least one track, so when there’s exactly one these read more naturally than ‘tracks.first`. They raise (ActiveRecord::SoleRecord exceeded) once the approval has fanned out, so a caller is never silently handed the wrong track — reach for `tracks` / `steps` then.
43 44 45 |
# File 'app/models/approval_engine/approval.rb', line 43 def track tracks.sole end |