Packages

The finite state machine implementations for Elixir.

Current section

Files

Jump to
aasm lib aasm.ex
Raw

lib/aasm.ex

defmodule AASM do
@moduledoc """
The finite state machine implementations for Elixir.
"""
defmacro aasm(field, body) do
quote do
alias Ecto.Changeset
def aasm_field do
unquote(field)
end
def current_state(record) do
case record |> Map.fetch(aasm_field()) do
{:ok, state} -> String.to_atom(state)
_ -> nil
end
end
defp validate_state_transition(changeset, from_states, to_state) do
cr_state = current_state(changeset.data)
if Enum.member?(from_states, cr_state) && state_defined?(to_state) do
changeset
else
changeset
|> Changeset.add_error(
aasm_field(),
"You can't move #{aasm_field()} from :#{cr_state} to :#{to_state}"
)
end
end
unquote(body[:do])
end
end
defmacro state(states) do
quote do
def states do
unquote(states)
end
def state_defined?(state) do
states()
|> Enum.member?(state)
end
end
end
defmacro event(event, opts, callback) do
quote do
alias Ecto.Changeset
def unquote(:"#{event}")(record) do
%{from: from_states, to: to_state} = unquote(opts)
record
|> Changeset.change(%{aasm_field() => Atom.to_string(to_state)})
|> validate_state_transition(from_states, to_state)
|> unquote(callback).()
end
def unquote(:"can_#{event}?")(record) do
%{from: from_states, to: to_state} = unquote(opts)
cr_state = current_state(record)
Enum.member?(from_states, cr_state) && state_defined?(to_state)
end
end
end
end