View Source Getting Started with State Machines

Get familiar with Ash resources

If you haven't already, read the Ash Getting Started Guide, and familiarize yourself with Ash and Ash resources.

Bring in the ash_state_machine dependency

{:ash_state_machine, "~> 0.2.7"}

Add the extension to your resource

use Ash.Resource,
  extensions: [AshStateMachine]

Add initial states, and a default initial state

use Ash.Resource,
  extensions: [AshStateMachine]

...

state_machine do
  initial_states [:pending]
  default_initial_state :pending
end

Add allowed transitions

state_machine do
  initial_states [:pending]
  default_initial_state :pending

  transitions do
    # `:begin` action can move state from `:pending` to `:started`/`:aborted`
    transition :begin, from: :pending, to: [:started, :aborted]
  end
end

Use transition_state in your actions

For simple/static state transitions

actions do
  update :begin do
    # for a static state transition
    change transition_state(:started)
  end
end

For dynamic/conditional state transitions

defmodule Start do
  use Ash.Resource.Change

  def change(changeset, _, _) do
    if ready_to_start?(changeset) do
      AshStateMachine.transition_state(changeset, :started)
    else
      AshStateMachine.transition_state(changeset, :aborted)
    end
  end
end

actions do
  update :begin do
    # for a dynamic state transition
    change Start
  end
end

Declaring a custom state attribute

By default, a :state attribute is created on the resource that looks like this:

attribute :state, :atom do
  allow_nil? false
  default AshStateMachine.Info.state_machine_initial_default_state(dsl_state)
  public? true
  constraints one_of: [
    AshStateMachine.Info.state_machine_all_states(dsl_state)
  ]
end

You can change the name of this attribute, without declaring an attribute yourself, like so:

state_machine do
  initial_states([:pending])
  default_initial_state(:pending)
  state_attribute(:alternative_state) # <-- save state in an attribute named :alternative_state
end

If you need more control, you can declare the attribute yourself on the resource:

attributes do
  attribute :alternative_state, :atom do
    allow_nil? false
    default :issued
    public? true
    constraints one_of: [:issued, :sold, :reserved, :retired]
  end
end

Be aware that the type of this attribute needs to be :atom or a type created with Ash.Type.Enum. Both the default and list of values need to be correct!

Making a resource into a state machine

The concept of a state machine (in this case a "Finite State Machine"), essentially involves a single state, with specified transitions between states. For example, you might have an order state machine with states [:pending, :on_its_way, :delivered]. However, you can't go from :pending to :delivered (probably), and so you want to only allow certain transitions in certain circumstances, i.e :pending -> :on_its_way -> :delivered.

This extension's goal is to help you write clear and clean state machines, with all of the extensibility and power of Ash resources and actions.