Simple Elixir State Machine for Validation
Turbo Panumarch
Posted on September 13, 2020
At some point, we will have an entity which the status can be transitioned to many possibilities and we want to have a rule to enforce an integrity of the data.
For example, this is a human emotion state change diagram.
And some transitions are okay, while some are not.
# 👌 OK
iex> update(%Human{emotion: :hungry}, %{emotion: :full})
{:ok, %Human{emotion: :full}}
# 🙅♂️ No, get something to eat first!
iex> update(%Human{emotion: :hungry}, %{emotion: :sleepy})
{:error, "invalid status change"}
🤖 Simple validation with pattern matching
For elixir developer, you can guess the pattern matching is pretty good for solving state change problem like this.
def update(%Human{} = human, attrs) do
with :ok <- validate_status(human.emotion, attrs.emotion),
...
end
def validate_status(current_status, new_status) do
case {current_status, new_status} do
# Put all the transition paths as a whitelist
{:hungry, :full} -> :ok
{:hungry, :angry} -> :ok
{:angry, :angry} -> :ok
{:angry, :full} -> :ok
{:full, :sleepy} -> :ok
# return error for the rest
_ -> {:error, "invalid status change"}
end
end
⚡️ Can we embed this rules into Ecto changeset?
If we want the rule to be strictly applied to the ecto data schema, we can also build a custom changeset validation for status change as well.
def changeset(human, attrs \\ %{}) do
human
|> cast(...)
|> validate_required(...)
|> validate_status_change() # <- custom validation
end
def validate_status_change(changeset) do
# Get current and new field data
current_status = changeset.data.status
new_status = get_field(changeset, :status)
case {current_status, new_status} do
# do nothing if ok
{:hungry, :full} -> changeset
{:hungry, :angry} -> changeset
{:angry, :angry} -> changeset
{:angry, :full} -> changeset
{:full, :sleepy} -> changeset
{nil, _} -> changeset # any new status is ok
# add error to ecto changeset errors
_ -> add_error(changeset, :status, "invalid status change")
end
end
That's it! Hope this could give you some idea in your next task. Feel free to discuss if you have any comments! 😊
💖 💪 🙅 🚩
Turbo Panumarch
Posted on September 13, 2020
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.