as_fsm v2.0.1 AsFsm behaviour
Implement Finite state machine in elixir
Usage
First you to define FSM module
defmodule TaskFsm do
use AsFsm, repo: MyApp.Repo
# by default state is check from column `state` of struct
# you can specify your own with
# use AsFsm, repo: MyApp.Repo, column: :status
# define your event
defevent(:start, from: :idle, to: :running)
defevent(:pause, from: :running, to: :paused)
defevent(:stop, from: [:running, :paused], to: :idle)
# you can define some hook
# it is automatically invoked if defined
def before_start(context) do
# do something then return context
context
end
def on_start(context) do
# do something then return context
context
end
end
All appropriate event function will be generated. In this example we have
def start(context), do: ....
def paus(context), do: ....
def stop(context), do: ....
Then use it
- Trigger an even transition
my_task
|> TaskFsm.new_context(other_params)
|> TaskFsm.start()
- Or trigger by name
my_task
|> TaskFsm.new_context(other_params)
|> TaskFsm.trigger(:start)
Understand the context
@type :: %Context{
struct: struct(),
state: any(),
valid?: boolean(),
error: String.t() | nil,
multi: Ecto.Multi.t() | nil
}
struct
is your datastate
any data you want to pass to transition, it could be parameter from clientvalid?
if it is true, then data will be persistederror
error message in casevalid?
is falsemulti
is anEcto.Multi
you can pass a multi tonew_context()
, it make sure all action you do in a transaction
Event hook
For each event you can define 2 hook
before_hook
you can define this hook to check for some condition before doing transationon_hook
this is your hook to do some logic on transaction
These 2 hooks must return a context. If you want to stop this transition, set valid?
to false and return the context.
Custom persist struct
You can define your own function to persist struct state. This function is run within Multi so that it must return {:ok, data} | {:error, reason}
def persist(struct, new_state, _context) do
# do your update logic
# or write log here
end
Link to this section Summary
Callbacks
Check if given event can be trigger with given state
Get event object for given event name
Check if there is any transition from state_a to state_b
List all events
List all available events for given state
Create new context
# new context without state data
new_context(struct)
Trigger event by event name
MyFsm.new_context(my_order, %{user: user})
|> MyFsm.trigger(:deliver)
Link to this section Types
context() :: %AsFsm.Context{ error: String.t() | nil, multi: Ecto.Multi.t() | nil, state: any(), struct: struct(), valid?: boolean() }
Link to this section Functions
Link to this section Callbacks
Check if given event can be trigger with given state
Get event object for given event name
Check if there is any transition from state_a to state_b
List all events
List all available events for given state
new_context(struct(), state :: any(), multi :: Ecto.Multi.t()) :: context()
Create new context
# new context without state data
new_context(struct)
# new context with data
new_context(struct, params)
# pass existing multi
new_context(struct, params, existing_multi)
Trigger event by event name
MyFsm.new_context(my_order, %{user: user})
|> MyFsm.trigger(:deliver)