StateMachine (state_machine v0.1.6)

StateMachine package implements state machine abstraction. It supports Ecto out of the box and can work as both data structure and a process powered by gen_statem.

Check out the article for motivation.

Here's an example of a simple state machine created with this package:

defmodule Cat do
  use StateMachine

  defstruct [:name, :state, hungry: true]

  defmachine field: :state do
    state :asleep
    state :awake
    state :playing
    state :eating, after_enter: &Cat.feed_up/1

    event :wake do
      transition from: :asleep, to: :awake
    end

    event :give_a_mouse do
      transition from: :awake, to: :playing, unless: &Cat.hungry/1
      transition from: :awake, to: :eating, if: &Cat.hungry/1
      transition from: :playing, to: :eating
    end

    event :pet do
      transition from: [:eating, :awake], to: :playing
    end

    event :sing_a_lullaby do
      transition from: :awake, to: :asleep
      transition from: :playing, to: :asleep
    end
  end

  def hungry(cat) do
    cat.hungry
  end

  def feed_up(cat) do
    {:ok, %{cat | hungry: false}}
  end
end

And later use it like this:

cat = %Cat{name: "Thomas", state: :asleep}

{:ok, %Cat{state: :awake}} = Cat.trigger(cat, :wake)

Features

  • Validation of state machine definition at compile time
  • Full support for callbacks (on states, events and transitions) and guards (on events and transitions)
  • Optional payload can be supplied with the event
  • One-line conversion to a state machine as a process (powered by gen_statem)
  • With Ecto support activated every transition is wrapped in transaction
  • With Ecto support activated the Ecto.Type implementation is generated automatically

Link to this section Summary

Link to this section Types

Specs

t(m) :: %StateMachine{
  events: %{optional(atom()) => StateMachine.Event.t(m)},
  field: atom(),
  misc: keyword(),
  state_getter: (StateMachine.Context.t(m) -> atom()),
  state_setter:
    (StateMachine.Context.t(m), atom() -> StateMachine.Context.t(m)),
  states: %{optional(atom()) => StateMachine.State.t(m)}
}