A second door out: rails g statecraft:from_aasm Order.
aasm already keeps the current state in a column, so this conversion is
the opposite shape of the statesman one — nothing is backfilled, and the
migration adds the history aasm never kept.
More survives the trip than from statesman. The generator reads the
live machine through reflection: the states, the initial state, the real
event names (statesman had none, so its skeleton arrived unnamed) and the
state column, which becomes the mounting's column:. A second argument
names one machine of a model that declares several.
Guards come over by nature. An aasm guard judges the record and never
sees what the caller submitted — which is exactly record_guard:. A
symbol guard:/if: therefore arrives as a record_guard: with a
generated one-line delegate, because a guard symbol resolves on the
machine here, not on the record:
event :cancel, from: :pending, to: :cancelled, record_guard: :not_locked?
private
def payable?(record) = record.payable?
def not_locked?(record) = !record.locked?
unless: becomes a negating delegate. A lambda guard carries its own
closure and cannot be moved: it arrives as a TODO with its file:line.
One graph shape is refused rather than guessed. aasm lets a single
event branch from one state and picks the first transition whose guard
passes; statecraft makes an event a partial function, so from is unique
within an event. The generator stops, names the branching events and asks
for the split — the pay / fail_payment pattern. Naming the halves is a
domain decision, and a bad name would outlive the migration.
One migration, creating the log table with its cascade FK and adding
an index on the existing state column. It deliberately does not tighten
that column: NOT NULL, a default and a CHECK on a live table mean long
locks and an explosion on any legacy row outside the state list. The
recipe for doing it later, in the NOT VALID → VALIDATE CONSTRAINT
style, sits in the migration's own header.
The reflection is grounded rather than assumed: every call the generator
makes was verified against aasm 5.5.2 — including the trap that a model
with named machines answers the unnamed .aasm with an empty :default
graph, which the generator now refuses while naming the machines that do
exist.
Also in this release. The README gained a section on which rules
belong in a guard at all: a guard earns the input it reads by comparing it
against the record, or by protecting a log row about to become permanent,
while shape and format belong to whoever assembled the hash. Metadata is
frozen before the transaction opens, so an in-guard check of it is no more
atomic than the caller's — form validation belongs in the form.