Skip to content

Actionable v1.7.0 — lifecycle and adapter hardening

Choose a tag to compare

@aemadrid-twx aemadrid-twx released this 22 Oct 23:40
· 57 commits to main since this release
46e854e

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/ and rake bench. A dependency-free script (monotonic
    clock, no benchmark / benchmark-ips) times a trivial run, a 5-step run with
    measure :none and :all, 5-field input coercion, a nested action with and
    without measurement, and history.to_json. BENCH_ITERATIONS sets 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

  • fail is not raise inside an action. USAGE (Control flow), README,
    getting_started, AGENTS.md and the skill now warn that the gem's fail verb
    replaces Kernel#fail in an action's instance methods: fail 'boom'
    records a Failure with code :"boom" and the run continues, and
    fail SomeError, 'msg' raises NoMethodError. Use raise for exceptions
    and fail!(: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), always hooks 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_config and after_commit / after_commit_hooks on
    Actionable::Action (Rails adapter), and the RSpec matchers
    (perform_actionable and its chain) and stubs (allow_actionable_*,
    stub_actionable_*, actionable_*_result). They are generated from YARD by
    sord like the core's (.yardopts now excludes only ProxyValidator, the
    Railtie and actionable/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_text list after_commit hooks. With the Rails
    adapter loaded, describe[:hooks][:after_commit] names them, and
    describe_text adds an after_commit: hooks line when there are any.

  • after_commit hook kind (Rails adapter). After require 'actionable/rails',
    after_commit :method (or a nested action, with :if / :unless /
    :input like any hook) runs a side effect only after the database work
    really committed. It is registered with
    ActiveRecord.after_all_transactions_commit when a run ends in a Success,
    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. A Failure or Skipped never 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 outer transaction call, or .run when no transaction was open).
    Needs ActiveRecord >= 7.2; declaring it on an older one (or without
    ActiveRecord loaded) raises a DefinitionError. Existing hooks and the
    core are unchanged.

  • honor_retry_metadata: let Actionable::Job act on declared retry:
    metadata.
    An action that calls it (the macro comes with
    require 'actionable/job') has the retry: key of its declared failures and
    skips decide the job's disposition: retry: true retries (a skip or a
    permanent-code failure included) and retry: false does not (a failure is
    discarded, a skip acks). Codes without the key map as before. Retried skips
    raise the same RetryableFailure, carrying the Skipped. Inherited; a
    subclass backs out with honor_retry_metadata(enabled: false).
    Actionable::Job.disposition takes the matching honor_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, nested step SomeAction, or on_success / on_failure /
    on_skip / always :x with 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.

  • always hooks now run even after an outcome hook halts. A bang verb
    (fail! / skip! / succeed!), halt!, or a failing nested action inside
    an on_success / on_failure / on_skip hook still has its result adopted
    and still stops the remaining hooks of that category, but it no longer skips
    the always hooks: 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 an always hook is adopted the same way and
    stops only the remaining always hooks. Under transactional, always
    hooks still run inside the transaction and the adopted result still decides
    commit or rollback, so an always hook's writes roll back with a failed run.
    When measuring, those always hooks appear in the history as usual.
    Upgrade note: an always hook that assumed it would not run after a hook
    failure must now check result itself.