Repository navigation
Actionable v1.7.0 — lifecycle and adapter hardening
Lifecycle and adapter hardening. always hooks now run even after an outcome
hook halts; redeclaring a step or hook overrides it in place; the Rails adapter
gains an opt-in after_commit hook (ActiveRecord >= 7.2) that runs only after
the outermost transaction commits; Job can opt in to honouring a failure's or
skip's declared retry:; the Rails, Job and RSpec surfaces now ship RBS. Two
behaviour changes have upgrade notes under Changed. Still requires
field_struct >= 0.12.
Added
benchmarks/andrake bench. A dependency-free script (monotonic
clock, nobenchmark/ benchmark-ips) times a trivial run, a 5-step run with
measure :noneand:all, 5-field input coercion, a nested action with and
without measurement, andhistory.to_json.BENCH_ITERATIONSsets the loop
count. Not in the default task and not packaged in the gem; a smoke spec
runs it with 5 iterations so it can't rot. README has a "Performance"
section with indicative numbers.
Documentation
-
failis notraiseinside an action. USAGE (Control flow), README,
getting_started, AGENTS.md and the skill now warn that the gem'sfailverb
replacesKernel#failin an action's instance methods:fail 'boom'
records aFailurewith code:"boom"and the run continues, and
fail SomeError, 'msg'raisesNoMethodError. Useraisefor exceptions
andfail!(:code)to stop with a failure. Behaviour is unchanged. -
README, getting_started, AGENTS.md and the skill now mention the 1.7.0
topics where they list their neighbours: step/hook redeclaration (last
wins),alwayshooks running after a hook halt,after_commit, and
honor_retry_metadata, each linking to USAGE. -
RBS for the adapters' public surface (owner ruling P11).
sig/now
declares what clients call from the adapters:transactional/
transaction_configandafter_commit/after_commit_hookson
Actionable::Action(Rails adapter), and the RSpec matchers
(perform_actionableand its chain) and stubs (allow_actionable_*,
stub_actionable_*,actionable_*_result). They are generated from YARD by
sord like the core's (.yardoptsnow excludes onlyProxyValidator, the
Railtie andactionable/rspec/coverage); the one runtime mix-in YARD cannot
see,after_commit's singleton-class prepend, is declared by hand in
sig/actionable/rails.rbs. A spec compares each adapter type's public
methods, by reflection, with its RBS so neither can drift.Actionable::Job
was already generated. The report-only Steep lane now checks the adapters
too. -
describe/describe_textlistafter_commithooks. With the Rails
adapter loaded,describe[:hooks][:after_commit]names them, and
describe_textadds anafter_commit:hooks line when there are any. -
after_commithook kind (Rails adapter). Afterrequire 'actionable/rails',
after_commit :method(or a nested action, with:if/:unless/
:inputlike any hook) runs a side effect only after the database work
really committed. It is registered with
ActiveRecord.after_all_transactions_commitwhen a run ends in aSuccess,
so it waits for the outermost open transaction (not the action's own
savepoint), runs immediately when none is open, and never runs if an
enclosing transaction rolls back. AFailureorSkippednever runs it.
The run is over by then: the hook can't change the result, isn't recorded in
the history, and an exception it raises propagates after the commit (from
the outertransactioncall, or.runwhen no transaction was open).
Needs ActiveRecord >= 7.2; declaring it on an older one (or without
ActiveRecord loaded) raises aDefinitionError. Existing hooks and the
core are unchanged. -
honor_retry_metadata: letActionable::Jobact on declaredretry:
metadata. An action that calls it (the macro comes with
require 'actionable/job') has theretry:key of its declared failures and
skips decide the job's disposition:retry: trueretries (a skip or a
permanent-code failure included) andretry: falsedoes not (a failure is
discarded, a skip acks). Codes without the key map as before. Retried skips
raise the sameRetryableFailure, carrying theSkipped. Inherited; a
subclass backs out withhonor_retry_metadata(enabled: false).
Actionable::Job.dispositiontakes the matchinghonor_retry:keyword.
Off by default, so metadata stays neutral (D20/D21) and existing jobs behave
exactly as before.
Changed
-
Redeclaring a step or hook now overrides it. A second
step :x,
case_step :x, nestedstep SomeAction, oron_success/on_failure/
on_skip/always :xwith the same type and name, in the same class or a
subclass, replaces the earlier declaration's options (guards,:input/
:expose, case branches) and keeps the earlier position. Before, the
redeclaration was silently dropped and the first declaration's options won,
so a subclass could not override an inherited step as invariant 5 promises.
A subclass's override never changes the parent. Upgrade note: an action
that redeclares a step with different options now runs the last options. -
alwayshooks now run even after an outcome hook halts. A bang verb
(fail!/skip!/succeed!),halt!, or a failing nested action inside
anon_success/on_failure/on_skiphook still has its result adopted
and still stops the remaining hooks of that category, but it no longer skips
thealwayshooks: they run next and see the adopted result. Before, one
halt stopped every remaining hook, so cleanup and audit hooks could be
silently skipped. A halt inside analwayshook is adopted the same way and
stops only the remainingalwayshooks. Undertransactional,always
hooks still run inside the transaction and the adopted result still decides
commit or rollback, so analwayshook's writes roll back with a failed run.
When measuring, thosealwayshooks appear in the history as usual.
Upgrade note: analwayshook that assumed it would not run after a hook
failure must now checkresultitself.