View Source Mimzy behaviour (Mimzy v2.1.0)

A module for working with finite-state machines and defining finite-state machine callbacks.

Unlike :gen_statem, which implements the machine as an Erlang process, this state machine can be implemented with a distributed storage mechanism such as a database.

example

Example

The following example shows a simple pushbutton model for a toggling pushbutton. You can push the button and it replies if it went on or off, and you can ask for a count of how many times it has been pushed to switch on.

The following is the complete callback module file push_button.ex:

defmodule PushButton do
  @behaviour Mimzy

  import Ecto.Query, only: [from: 2]

  @impl Mimzy
  def handle_event(:create) do
    {1, [%{id: id}]} = Repo.insert_all("buttons", [[count: 0, state: "off"]], returning: [:id])
    id
  end

  @spec handle_event(Mimzy.id(), Mimzy.event()) :: term
  def handle_event(id, event) do
    {:ok, result} = Repo.transaction(fn -> Mimzy.handle_event(id, event, __MODULE__) end)
    result
  end

  @impl Mimzy
  def init(id) do
    case Repo.one(
           from b in button_query(id),
             lock: "FOR UPDATE",
             select: {b.state, b.count}
          ) do
      {state, count} ->
        {:cont, state, count}

      nil ->
        {:halt, :error}
    end
  end


  @impl Mimzy
  def handle_event("off", :push, id, count) do
    id
    |> button_query()
    |> Repo.update_all(set: [count: count + 1, state: "on"])

    :on
  end

  def handle_event("on", :push, id, count) do
    id
    |> button_query()
    |> Repo.update_all(set: [count: count, state: "off"])

    :off
  end

  def handle_event(_state, {:put_count, new_count}, id, _count) do
    id
    |> button_query()
    |> Repo.update_all(set: [count: new_count])

    :ok
  end

  def handle_event(_state, :delete, id, _count) do
    {1, nil} =
      id
      |> button_query()
      |> Repo.delete_all()

    :ok
  end

  def handle_event(_state, :get_count, _id, count),
    do: count

  @spec button_query(Mimzy.id()) :: Ecto.Query.t()
  defp button_query(id) do
    from b in "buttons", where: b.id == ^id
  end
end

Usage would be:

id = PushButton.handle_event(:create)
#=> 123

PushButton.handle_event(id, :get_count)
#=> 0

PushButton.handle_event(id, :push)
#=> :on

PushButton.handle_event(id, :get_count)
#=> 1

PushButton.handle_event(id, :push)
#=> :off

PushButton.handle_event(id, {:put_count, 99})
#=> :ok

PushButton.handle_event(id, :get_count)
#=> 99

PushButton.handle_event(id, :delete)
#=> :ok

PushButton.handle_event(id, :push)
#=> :error

Link to this section Summary

Callbacks

Called when a state machine event is ready to be handled and the machine is in a nonexistent or pseudo state.

This function is called after init/1.

Called when a state machine event is ready to be handled.

Functions

Handles a state machine event.

Link to this section Types

@type data() :: any()
@type event() :: any()
@type id() :: any()
@type state() :: any()

Link to this section Callbacks

Link to this callback

handle_event(event)

View Source (optional)
@callback handle_event(event()) :: term()

Called when a state machine event is ready to be handled and the machine is in a nonexistent or pseudo state.

This is the function that would create a new finite-state machine.

Link to this callback

handle_event(state, event, id, data)

View Source
@callback handle_event(state(), event(), id(), data()) :: term()

This function is called after init/1.

This is the function that would transition a finite-state machine from one state to another.

@callback init(id()) :: {:cont, state(), data()} | {:halt, term()}

Called when a state machine event is ready to be handled.

This function is called before handle_event/4 in order to retrieve the state and any machine-associated data.

The return value is expected to be

  • {:cont, state, data} to continue the event handling
  • {:halt, term} to halt the event handling and return the term

Link to this section Functions

Link to this function

handle_event(id, event, module)

View Source
@spec handle_event(id(), event(), module()) :: term()

Handles a state machine event.

This function delegates to the callbacks implemented in the given module.