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! 😊
Top comments (0)