Class: ApprovalEngine::Track
- Inherits:
-
ApplicationRecord
- Object
- ActiveRecord::Base
- ApplicationRecord
- ApprovalEngine::Track
- Defined in:
- app/models/approval_engine/track.rb
Overview
One track of an approval (e.g. “Finance” or “Legal”). A track holds ordered layers of steps; a layer resolves once its consensus policy is met, which activates the next layer or completes the track.
All progression happens synchronously inside the acting step’s lock, so a track is never observed in a half-advanced state.
Constant Summary collapse
- STATUSES =
%w[pending approved rejected cancelled].freeze
- OPEN_STEP_STATUSES =
%w[waiting pending].freeze
Instance Method Summary collapse
-
#advance!(step) ⇒ Object
A step was approved or rejected: re-evaluate its layer and short-circuit.
-
#advance_after_changes_requested!(step) ⇒ Object
An approver requested changes: cancel this iteration’s open work and append a fresh iteration.
-
#layer_tally(layer, iteration: steps.maximum(:iteration)) ⇒ Object
The live consensus tally for one layer (within an iteration) — the same facts ‘advance!` decides on, exposed as a read so a host UI can show “N of M approved” and why a layer is met/failed/undecided without re-deriving the consensus math (which only the engine should own).
Instance Method Details
#advance!(step) ⇒ Object
A step was approved or rejected: re-evaluate its layer and short-circuit. The layer resolves the moment its consensus is met, fails the moment its consensus is unreachable, and otherwise waits for more votes. Both approve! and reject! funnel through here so rejection respects the layer’s consensus policy instead of being a blanket veto.
27 28 29 30 31 32 33 34 35 36 37 |
# File 'app/models/approval_engine/track.rb', line 27 def advance!(step) layer_steps = steps.for_iteration(step.iteration).for_layer(step.layer) case layer_outcome(layer_steps) when :met cancel_steps(layer_steps.pending) # remaining votes are no longer needed activate_next_layer(step) || complete! when :failed fail! end end |
#advance_after_changes_requested!(step) ⇒ Object
An approver requested changes: cancel this iteration’s open work and append a fresh iteration. The track stays pending.
41 42 43 44 |
# File 'app/models/approval_engine/track.rb', line 41 def advance_after_changes_requested!(step) cancel_open_steps! IterationBuilder.build_next_iteration!(step) end |
#layer_tally(layer, iteration: steps.maximum(:iteration)) ⇒ Object
The live consensus tally for one layer (within an iteration) — the same facts ‘advance!` decides on, exposed as a read so a host UI can show “N of M approved” and why a layer is met/failed/undecided without re-deriving the consensus math (which only the engine should own).
track.layer_tally(1)
# => { required: 2, approved: 1, rejected: 0, pending: 2, waiting: 0,
# group_size: 3, outcome: :undecided }
Defaults to the track’s latest iteration. A layer that hasn’t opened yet (all steps still ‘waiting`) reads as `:undecided`, not `:failed`. Returns a zeroed `:undecided` tally for a layer that has no steps.
58 59 60 |
# File 'app/models/approval_engine/track.rb', line 58 def layer_tally(layer, iteration: steps.maximum(:iteration)) tally_for(steps.for_iteration(iteration).for_layer(layer)) end |