Sitelet https://github.com/aasm/aasm/issues/885
Skip to content

Support before/after callbacks on individual transitions #885

Description

@Ishaan453

AASM allows before: / after: on an event, but not on a single transition inside that event when the event has multiple possible destinations. That becomes a problem when one event fans out to several states and only one path should run certain side effects.

Example:

event :approve do
  transitions from: :pending, to: :active, if: :prepaid?
  transitions from: :pending, to: :awaiting_payment
end

We want finalize_activation to run only when going to :active.

If we do:

event :approve, before: :finalize_activation do
  # ...
end

finalize_activation also runs when the record goes to :awaiting_payment, which is wrong.

Event-level before: is fine when every transition shares the same kind of outcome (e.g. pay always ends in :active). It doesn’t work cleanly for branching events.

Describe the solution you'd like

Allow callbacks on the transition itself:

event :approve do
  transitions from: :pending, to: :active,
              if: :prepaid?,
              before: :finalize_activation,
              after: :notify_activated
  transitions from: :pending, to: :awaiting_payment
end

Expected behavior:

  1. Transition callbacks run only for the transition that was selected.
  2. Document how they compose with event-level callbacks (e.g. event before → transition before → change state → transition after → event after).

Describe alternatives you've considered

  1. Event-level before + condition inside the callback
    e.g. finalize_activation if prepaid?: duplicates guards and breaks easily if transition order changes.

  2. Use transition after: as a workaround
    Often works, but it’s not a real before, (runs after the state change).

  3. Split into separate events
    e.g. approve_prepaid vs approve: This avoids the issue but duplicates events and call sites.

Additional context

This comes up whenever an event is a single user/system action (“approve”, “submit”, “complete”) but business rules send the record down different paths with different side effects. Transition-scoped callbacks would make that model natural without splitting events or overloading event-level hooks.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions