Skip to content

Releases: Paymentbox-com/actionable

Actionable v1.10.0

Choose a tag to compare

@aemadrid-twx aemadrid-twx released this 25 Oct 17:59
3b6399c

Ruby 3.3 or newer is now required; the library itself is unchanged, so on
Ruby 3.3+ this is a drop-in bump from 1.9.0 (UPGRADING.md "1.9.0 → 1.10.0").
The rest is docs: every Ruby block in the docs is now parsed by a spec, USAGE
explains Result#status vs code and struct-typed input errors, and the README
installs from RubyGems. CI runs on our own self-hosted runners.

Changed

  • Ruby 3.3 or newer is required (required_ruby_version >= 3.3.0, was
    >= 3.2.0). 3.3 is the oldest Ruby a known client runs, and CI is moving to
    self-hosted Ubuntu 26.04 runners. The library code is unchanged; CI drops the
    3.2 lane and RuboCop targets 3.3.
    Upgrade note: on Ruby 3.2, stay on 1.9.x.

Fixed

  • A spec now parses every ```ruby block in README, USAGE, UPGRADING, AGENTS,
    the skill and docs/, doctest or not, so an example can't rot into invalid
    Ruby unseen. The one it caught, a `def … = ...` placeholder in UPGRADING's
    core-method-name entry, is now real Ruby.

Documentation

  • USAGE "Results" says what status is and that a Success's code is
    :success; USAGE "Input" shows a FieldStruct class as an input field and how
    an invalid one is reported (errors names only the field). Both doctested;
    gaps found by the docs-site tutorial.
  • README installs from RubyGems (gem 'actionable-rb'); 1.9.0 is published there.

Actionable v1.9.0

Choose a tag to compare

@aemadrid-twx aemadrid-twx released this 25 Oct 02:07
3ec6a7b

The gem is renamed actionable-rb so it can be published to rubygems.org,
where actionable belongs to an unrelated 2014 gem. Nothing in the library
changed: require 'actionable', the Actionable module and every API stay the
same. Two lines in an app change: the Gemfile's gem 'actionable' becomes
gem 'actionable-rb', and a .rubocop.yml that loads the cop with plugins:
renames its key to actionable-rb. Read UPGRADING.md "1.8.2 → 1.9.0".

Changed

  • The gem is renamed actionable-rb, so it can be published to rubygems.org,
    where actionable belongs to an unrelated 2014 gem. require 'actionable',
    the Actionable module and every API are unchanged. A new
    lib/actionable-rb.rb lets Bundler.require load it by the gem name, and the
    gemspec now pushes to rubygems.org instead of GitHub Packages.
    Upgrade note: the Gemfile line becomes gem 'actionable-rb', and a
    .rubocop.yml that loads the cop with plugins: must use the actionable-rb
    key.

Actionable v1.8.2 — review fixes

Choose a tag to compare

@aemadrid-twx aemadrid-twx released this 23 Oct 16:31
51f4f65

Three fixes found by a mutation-driven review, plus upgrade-guide answers from
a dry run against a large app. All three change what an app can see, so read
UPGRADING.md "1.8.1 → 1.8.2" before bumping. case_step branches now honour
if: / unless:: a guarded-out branch does nothing, silently, where the guard
used to be ignored. Actionable::RBS generates valid class names for ? / !
codes and raises on colliding ones. The first-run verb check sees through
alias_method, so it can raise on an aliased step it used to skip.

Fixed

  • case_step branches honour if: / unless:. on 'x', :handle, if: :ready? (and default :y, unless: …) accepted the guard and then ignored
    it, so the branch always ran. The branch is still picked by value alone; its
    guard now decides whether the target runs, as for a plain step. A matched
    branch whose guard opts out does nothing: no fall-through to the next branch
    or the default. Upgrade note: a branch you guarded now really skips.
  • Actionable::RBS names output classes for ? / ! codes validly.
    Under typed_outcomes, fails(:expired?) { … } emitted class Expired?FailureOutput, which RBS can't parse. A trailing ? / ! now
    becomes Predicate / Bang (ExpiredPredicateFailureOutput,
    DoneBangSkipOutput), so :x and :x? get different classes. generate
    now raises ArgumentError when two codes of one kind would share a class
    name (:ready / :ready_), which silently emitted one class for two
    schemas, or when a code can't start a class name. Upgrade note: rename
    one of two clashing codes.
  • The first-run verb check sees through alias_method. A step naming an
    alias (step :charge with alias_method :charge, :do_charge) looked up
    the def by the alias name, found none, and skipped the check; it now reads
    the original def, so a misplaced verb there raises DefinitionError like
    any other step. Documented limit: a module prepended over a step method
    hides the class's own def from the check. Upgrade note: fix the
    reported verb, as for any verb-position offense.

Documentation

  • UPGRADING.md, from a dry run against a large app (133 actions). The guide
    now says which six Finds must run on a green suite (silent behaviour changes)
    and that the rest cover code the suite doesn't reach. The describe Find skips
    RSpec.describe (1,552 hits before). The code Finds skip receivers and
    non-Ruby files. The @result entry says a Result you build yourself is still
    allowed, with halt!. A new section lists the class-level API unchanged since
    1.5.1. The cop entry says to list every base class defined in its own file. The
    opt-out goes in spec_helper, not an initializer. The effort estimate counts
    suite runs, and there is a PCRE2 check. AGENTS.md and the skill follow.

Actionable v1.8.1 — housekeeping

Choose a tag to compare

@aemadrid-twx aemadrid-twx released this 23 Oct 15:43
27bb639

Housekeeping. The Actionable/VerbNotLast cop resolves superclass names the way
Ruby does, so same-named classes in one file no longer confuse it; the
UPGRADING.md drift guard is fixture-tested and now asks for an explicit
decision on every visible CHANGELOG entry. No runtime behaviour changes.

Changed

  • Actionable/VerbNotLast resolves a superclass by its lexical full name.
    It matched by the last name segment, so in one file module B; class Base; end; class X < Base counted as an action when module A held a Base < Actionable::Action, and a file's own Legacy::ApplicationAction counted as
    the listed base. It now looks the name up as Ruby does (enclosing module /
    class outwards, a compact class A::B opens only A::B, ::X is the
    top level), so those classes are left alone, and a listed full name is
    found from inside its namespace (module Ops; class X < BaseAction with
    Ops::BaseAction listed). An offense it newly reports is a call the
    first-run check already raises on.

Tests / CI

  • The UPGRADING.md drift guard asks for a decision on every visible
    change.
    Each [Unreleased] bullet under ### Changed, ### Removed or
    ### Breaking… must carry an upgrading: <slug> marker comment (with its
    guide entry, as before) or upgrading: none, so a change filed there
    without an upgrade note no longer slips past it. Released sections are not
    re-checked. The guard's own examples now run on fixtures, so they hold in
    any release state; one example still checks the live files.

Actionable v1.8.0 — verb guard and upgrade guide

Choose a tag to compare

@aemadrid-twx aemadrid-twx released this 23 Oct 01:06
5f8d24d

A guard against silently continuing runs, plus upgrade guidance. A plain
fail / fail_with / skip / succeed must now be the last thing its step
or hook does — checked once per action class on first run (Prism; turn it off
with Actionable.verb_position_check = false) and by a shipped RuboCop cop,
Actionable/VerbNotLast. fail / skip codes must be identifiers, so the
fail 'card expired' Kernel habit raises instead of recording a code. Both can
raise on code that used to run: read UPGRADING.md, which walks 1.5 → 1.8
change by change. CI now also runs real ActiveJob/Sidekiq and ActiveRecord 7.1
lanes. Still requires field_struct >= 0.12.

Added

  • A plain recording verb must end its step or hook method (owner rulings
    R5, R10).
    fail :card_expired if card.expired? followed by more code keeps
    running — almost always a fail! missing its bang. On an action's first
    run, every step method (case-step branch targets included) and lifecycle
    hook method (on_success / on_failure / on_skip / always, and
    after_commit with the Rails adapter) is parsed with Prism, and a plain
    fail / fail_with / skip / succeed (or self.fail …) that isn't the
    method's last expression raises DefinitionError naming the action, method, file:line,
    verb and fix. Branches, return fail(...), a verb followed by return,
    and blocks whose call is last are allowed; USAGE (Control flow, "Where a
    plain verb may go") has the full table. Checked once per class: later runs
    pay one instance-variable read. Skipped without Prism >= 1.0 (Ruby 3.2
    without the prism gem; no new dependency) and for methods with no
    parseable source (eval, define_method).
  • Actionable.verb_position_check = false turns that first-run check off
    for the process (on by default; Actionable.verb_position_check? reads it).
    Off, run never parses source or loads Prism. It is an escape hatch while
    upgrading, and leaves the cop as the only guard. The identifier check on
    fail / skip codes stays on: it's a per-call regex, not a parse.
  • Actionable/VerbNotLast, an opt-in RuboCop cop with the same rules
    (one shared analyzer). Not loaded by require 'actionable'; enable it with
    plugins: [{actionable: {require_path: actionable/rubocop}}] (RuboCop >=
    1.72, via the gem's lint_roller plugin) or require: [actionable/rubocop].
    It checks a class whose superclass is an action base and that declares
    step / case_step, and the methods that class names in its declarations.
    An action base is one of the cop's ActionBaseClasses (default
    Actionable::Action, ApplicationAction) or a class defined in the same
    file whose superclass chain reaches one, so a dry-transaction class with
    its own step gets no false positives (owner ruling R10).
  • rake bench gains fail! action and skip! action scenarios.

Changed

  • fail / skip codes must be identifiers. A code that isn't a Symbol
    or String matching /\A[[:alpha:]_][[:alnum:]_]*[?!]?\z/ ('card expired', :"not-found", 'payment.declined', a class) raises
    ArgumentError at the call, suggesting raise for Kernel#fail habit.
    Applies to fail / fail! / fail_with / fail_with! / skip / skip!.

Upgrading: both checks raise on code that used to run.

  • DefinitionError "… calls fail with more code after it …" on an action's
    first run: if the code after the verb should not run, use the bang verb
    (fail!); if it should, move the verb to the end of the method (or
    return fail(...)). Run the cop over your app to find them all up front.
    If you can't fix them all before deploying, set
    Actionable.verb_position_check = false in an initializer until you have.
  • ArgumentError "… code "…" is not an identifier": use a Symbol code and
    pass the sentence as the message (fail(:card_expired, 'card expired')),
    or raise if you meant an exception.

Tests / CI

  • Real-backend lanes. Two new CI jobs feed the required ci check:
    jobs (ActiveJob + Sidekiq) runs the suite with gemfiles/jobs.gemfile, so
    spec/actionable/job_backends/ proves Job + honor_retry_metadata on a
    real ActiveJob (retry_on Actionable::Job::RetryableFailure: retry: true
    re-enqueues until attempts run out, retry: false discards or acks, the
    default is unchanged) and that RetryableFailure propagates out of a Sidekiq
    job in inline testing mode (no Redis). activerecord 7.1 runs the suite on
    ActiveRecord 7.1 (gemfiles/activerecord_7_1.gemfile, Ruby 3.3):
    transactional and savepoints work there, and declaring after_commit
    raises the DefinitionError naming 7.2. These specs skip in the default
    suite, where the gems or versions they need are absent.

Documentation

  • UPGRADING.md: how to move an app from 1.5.x to 1.8.0 (owner ruling
    R9). A TL;DR with the order of work (field_struct first, then actionable,
    the cop, RBS), a straight 1.5-to-1.8 path, a symptom index, and a Symptom /
    Find / Fix / Why entry for every change a caller can see, each Find an rg
    pattern checked against its "before" code or the tool that finds it. AGENTS.md
    and the skill gain a numbered upgrade procedure for agents; README and
    getting_started link to it, and the gem ships it. spec/upgrading_spec.rb
    keeps it honest: every CHANGELOG upgrade note since 1.5.2 carries an
    invisible upgrading: slug comment that the guide must carry in that
    version's section, and the guide's declared range must reach the next
    release. Its runnable examples are doctested.
  • README "Types (RBS)" says the adapter signatures (transactional,
    after_commit, the RSpec helpers, Job) are declared on Actionable::Action
    even when actionable/rails isn't required: RBS can't make a signature
    conditional and rbs loads a gem's whole sig/.

Actionable v1.7.0 — lifecycle and adapter hardening

Choose a tag to compare

@aemadrid-twx aemadrid-twx released this 22 Oct 23:40
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.

Actionable v1.6.0 — valid, guarded RBS

Choose a tag to compare

@aemadrid-twx aemadrid-twx released this 22 Oct 23:01
35e06aa

Valid, guarded RBS. The gem's own signatures can only get more typed (CI
ratchet, untyped 46 → 27); generated RBS for your actions declares the real
superclass chain, a typed InputSchema, typed .run keywords and output
ivars, built on field_struct 0.12's FieldStruct::RBS.type_for. Requires
field_struct >= 0.12. No runtime behaviour changes.

Added

  • Actionable::RBS.generate types an action's input and output instance
    variables
    (generated RBS only; no runtime change). For an action with a
    fixed input schema it emits a nested class InputSchema < ::FieldStruct::Base
    with a typed reader and writer per input field, def input: () -> InputSchema,
    and a .run overload | (InputSchema input) -> ... beside the keyword form.
    InputSchema exists only in RBS (the runtime schema is anonymous), so
    generation raises ArgumentError when Ruby already resolves
    YourAction::InputSchema (a constant on the action, an ancestor or an
    included module). It also declares each output field as a nullable
    instance variable (@total: ::Integer?), so a step that assigns
    @total = ... type-checks. Upgrade note: the generated RBS now owns
    those instance variables; delete any declaration of them from your own sig
    files, since RBS rejects an instance variable declared twice.

Fixed

  • Actionable::RBS.generate declares the real superclass chain. An action
    under an intermediate base (class Codes::DecideMapping < ApplicationAction)
    was declared < ::Actionable::Action, hiding its bases from the type
    checker, and a host that declared the real superclass got
    NoSuperclassFoundError / a failing rbs validate when the base had no
    signature. The action now names its nearest named superclass, and every
    named ancestor up to Actionable::Action is declared first, member-less
    (a typed one with its empty result classes), so the RBS resolves on its own.
    The declarations repeat byte-identically across files and merge. A namespace
    that is a class now opens as class, not module, which RBS rejected as a
    duplicate declaration.
    Upgrade note: if your own sig files declare one of those bases with a
    different superclass (e.g. class ApplicationAction < ::Actionable::Action
    while Ruby says otherwise), RBS now reports a superclass mismatch; make your
    declaration match Ruby's ancestry or drop it.

Changed

  • Optional .run keywords are nullable in generated RBS (?note: ::String?): an optional field may be passed nil explicitly, and the run succeeds.
  • A required input field with a default is an optional .run keyword (?amount: ::Integer), since run succeeds without it. An action whose input is chosen by an input_for dispatch (including one inherited under a fixed input) gets .run: (*untyped), since the dispatch decides the keywords at run time.
  • Requires field_struct >= 0.12. The generator now takes every field type
    from FieldStruct::RBS.type_for instead of its own copy of the mapping, so
    generated action RBS matches FieldStruct::RBS.generate. Consumer-visible
    changes to generated RBS: writers on Output and the per-code output
    classes are typed (T? value) -> T? instead of (untyped value) -> untyped; scalar array elements are nullable (::Array[::String?]); a
    :value field is untyped, never untyped?; a bare generic ruby_type
    gets type arguments (::Hash[untyped, untyped]); and a field's rbs_type:
    override is honoured. A field typed by an anonymous nested struct class now
    raises ArgumentError (it previously emitted an invalid :: name).
  • The committed signatures can only get more typed. Contributor tooling
    only; the shipped sig/actionable.rbs is unchanged. rake sigs:untyped
    (part of the default task) fails when sig/ holds more or fewer than the
    committed ceiling of 46 non-comment untyped, so a typing change lowers
    the ceiling with it. sigs:generate / sigs:check fail on a sord
    [OMIT]/[DUCK] fallback that isn't in the committed allowlist (24
    today) and on allowlist entries sord no longer emits. sord runs without
    --replace-errors-with-untyped (the output is identical) and fails on any
    SORD_ERROR_ type. sigs:validate validates the whole sig/ directory.
  • Optional, report-only Steep 2.x via gemfiles/types.gemfile and a
    types CI job on Ruby 3.4 that never fails the build. The Steepfile now
    checks the core only (the RSpec and Rails adapters are ignored); Steep is
    no longer in the main Gemfile.
  • More precise signatures (RBS only; no runtime change). Keyword splats
    are typed per value (**Object options, not **::Hash[Symbol, Object] options, which RBS reads as "every value is a Hash"); .run, #initialize
    and Job::Mixin#run_actionable take *Object, **Object; the gem's
    constants (Actionable::HALT, VERSION, Action::GEM_FAILURE_CODES, …)
    are declared; and the halt/measurement helpers declare their block. The
    DSL methods whose block runs with another self (output, input,
    fails, skips, case_step, input_for, and the define builders)
    gain a block overload with its self-type in the new
    sig/actionable/dsl.rbs. untyped in sig/ falls from 46 to 27, and the
    sord fallback allowlist from 24 entries to 4.

v1.5.2 — runner and nested-action fixes

Choose a tag to compare

@aemadrid-twx aemadrid-twx released this 22 Oct 21:58
76f3d76

Runner and nested-action fixes: hooks that halt, kwargs that survive hooks,
nested actions that neither wipe parent ivars nor commit failed writes.
Requires field_struct 0.11.0 or later.

Changed

  • Requires field_struct >= 0.11. It is the release whose numeric and input
    fixes this one is tested against; CI now pins it by tag.
  • CI runs Ruby 3.2, 3.3, 3.4 and 4.0 (was 3.3.6 only), plus the docs guard,
    behind one aggregate ci check that main requires.
  • The packaged gem leaves out contributor-only files (.claude/,
    CLAUDE.md, docs/origin/, Rakefile/Steepfile/lint config). It still ships
    lib/, sig/, the user docs, AGENTS.md and the Claude skill.

Fixed

  • A failed transactional action inside an outer transaction no longer
    commits its writes.
    It joined the outer transaction, whose commit ignored
    the action's rollback, so a recorded Failure still committed its rows
    (measured on ActiveRecord 8.1 + SQLite). Inside an open transaction the action
    now takes its own savepoint (requires_new: true) unless requires_new: is
    given; requires_new: false keeps the old joining behaviour.

  • A nested action no longer wipes parent ivars it never set. Absorbing a
    successful or skipped child copied every declared output field, so a field
    the child left nil (or at its default) overwrote the parent's value. Only
    fields the child actually set are absorbed now; expose: still narrows them.

  • Private step, hook and case_step methods work. The definition check
    already accepted them, but the step then raised NoMethodError; they are
    now called like guards are. A step or hook named after a method the action
    only inherits from Ruby's core (step :puts, always :exit) raises
    DefinitionError up front instead of calling Kernel.

  • Action.input without a block reads the schema instead of replacing it
    with an empty one, matching output.

  • A step that assigns @result gets a clear DefinitionError instead of a
    NoMethodError deep in the runner.

  • History#to_json requires Oj itself rather than relying on another file
    having loaded it.

  • A bang verb inside a lifecycle hook now sets the result. fail!,
    skip! or succeed! in a hook used to be dropped: fail! in on_success
    returned Success, committed a transactional action and acked the job.
    The hook's result is now adopted before the halt stops the remaining hooks,
    as the docs always said. This includes a nested action used as a hook
    (on_success ChildAction): a failing child now fails the run and stops the
    remaining hooks.

  • succeed/skip kwargs survive lifecycle hooks. After any hook
    refreshed the output, the kwargs were lost: a required field set only by
    kwarg turned the run into Failure(:invalid_output), and a kwarg that
    overrode an ivar silently reverted to the ivar's value. Present since 1.0.0.
    A Failure(:invalid_output) also keeps the output that failed validation
    across hooks instead of having it rebuilt from ivars.

  • No spurious errors[:output] on a failure or skip. The best-effort,
    unvalidated output of a non-typed failure or skip added
    output: ["is invalid"] to the result's errors. It no longer does; errors
    the failure recorded itself are kept.

v1.5.1

Choose a tag to compare

@aemadrid-twx aemadrid-twx released this 22 Oct 22:23

Added

  • Coverage.new_gap_hint sets the advice the baseline-mismatch message
    gives for a new gap. The default (Coverage::DEFAULT_NEW_GAP_HINT) is
    unchanged; a host whose baseline only shrinks can drop "or add it to the
    baseline".

Fixed

  • RBS for a subclass of a typed action now resolves on its own. Its result
    classes subclass the parent's (< ::ApiAuth::BaseAction::Success), which
    nothing declared when the parent is an abstract base that is never
    generated. RBS.generate now declares those parent classes, empty, up the
    chain of typed ancestors.
  • RBS readers on Failure are nullable. An invalid_output failure
    carries the output that broke its schema, so a required field can be
    missing; the readers were typed non-nil.

v1.5.0

Choose a tag to compare

@aemadrid-twx aemadrid-twx released this 21 Oct 22:15
fabec77

Typed outcomes (decision D23). Opt-in with typed_outcomes: an action that
doesn't turn it on runs as before, except that a code whose fails or skips
declaration carries a block (new syntax) has its output validated.

Added

  • typed_outcomes turns typed outcomes on for an action and its
    descendants (typed_outcomes enabled: false turns them off for a subclass);
    typed_outcomes? reads it.
  • A block on fails / skips declares the output a result with that code
    carries, in the output DSL. failure_output_schema(code) and
    skip_output_schema(code) read it back, and describe shows its fields.
    Such output is built from the call's keywords alone, never from instance
    variables, and validated: output that breaks the schema ends the run as
    invalid_output. The block also ends instance-variable capture for that
    code, typed outcomes or not.
  • output: on fail / fail! / fail_with / fail_with!, under typed
    outcomes only: the failure's output, a Hash or a FieldStruct
    (ArgumentError otherwise). Without them output: on fail is still an
    error, and fail_with refuses it. Under typed outcomes, output for a code
    with no schema raises DefinitionError, naming the code.
  • Per-action result classes. Under typed outcomes .run returns the
    action's own Success, Failure or Skipped, each a subclass of the gem
    class (or of the parent action's own). A nested failure becomes the parent's
    own Failure, judged by the parent's schema for its code; a parent without
    typed outcomes gets a plain Actionable::Failure. The RSpec stub helpers
    return them too, with output shaped through the code's schema (or empty
    without one) as a real run would.
  • RBS: Actionable::RBS.generate types a typed action's .run as
    (Success | Skipped | Failure), with each code and output narrowed and
    the output's readers declared on each class. A host's own result subclass,
    which .run still returns as it is, is not described: a known gap.

Changed

  • Under typed outcomes, a skip or failure whose code has no schema carries an
    empty output, where it used to carry a best-effort capture into the success
    schema. The gem's own invalid_input / invalid_output keep theirs.
  • Under typed outcomes, results compare by class: Act::Success is not ==
    to an Actionable::Success with the same output, and the stub helpers
    return a new object of the action's own class. Specs that compare results
    with == need to compare against the action's class.