View Source Getting Started with State Machines

get-familiar-with-ash-resources

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

Bring in the ash_state_machine dependency

def deps()
  [
    ...
    {:ash_graphql, "~> 0.1.0"}
  ]
end

making-a-resource-into-a-state-machine

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.

a-basic-state-machine

A basic state machine

defmodule Order do
  # leaving out data layer configuration for brevity
  use Ash.Resource,
    extensions: [AshStateMachine]

  state_machine do
    initial_states [:pending]
    default_initial_state :pending

    transitions do
      transition :confirm, from: :pending, to: :confirmed
      transition :begin_delivery, from: :confirmed, to: :on_its_way
      transition :package_arrived, from: :on_its_way, to: :arrived
      transition :error, from: [:pending, :confirmed, :on_its_way], to: :error
    end
  end

  actions do
    # create sets the state
    defaults [:create, :read]

    update :confirm do
      # accept [...] 
      # you can change other attributes
      # or do anything else an action can normally do
      # this transition will be validated according to
      # the state machine rules above
      change transition_state(:confirmed)
    end

    update :begin_delivery do
      # accept [...]
      change transition_state(:on_its_way)
    end

    update :package_arrived do
      # accept [...]
      change transition_state(:arrived)
    end

    update :error do
      accept [:error_state, :error]
      change transition_state(:error)
    end
  end

  changes do
    # any failures should be captured and transitioned to the error state
    change after_transaction(fn
              changeset, {:ok, result} ->
                {:ok, result}

              changeset, {:error, error} ->
                message = Exception.message(error)

                changeset.data
                |> Ash.Changeset.for_update(:error, %{
                  message: message,
                  error_state: changeset.data.state
                })
                |> Api.update()
            end),
            on: [:update]
  end

  attributes do
    uuid_primary_key :id
    # ...attributes like address/delivery options would go here
    attribute :error, :string
    attribute :error_state, :string
    # :state attribute is added for you by `state_machine`
    # however, you can add it yourself, and you will be guided by
    # compile errors on what states need to be allowed by your type.
  end
end

generating-flow-charts

Generating Flow Charts

run mix ash_state_machine.generate_flow_charts to generate flow charts for your resources. See the task documentation for more. Here is a chart generated from the example above:

flowchart TD
pending --> |confirm| confirmed
confirmed --> |begin_delivery| on_its_way
on_its_way --> |package_arrived| arrived
on_its_way --> |error| error
confirmed --> |error| error
pending --> |error| error

learning-more

Learning more