EctoTransit
EctoTransit is a transition validator for Ecto and EctoEnum
Installation
Add ecto_transit in your mix.exs and run mix deps.get
def deps do
[
{:ecto_transit, "~> 0.1.0"}
]
endHow to use
Generate validator
defmodule TODO do
@states ~w(created scheduled doing overdued done closed)a
@transitions %{
:* => :closed,
:created => ~w(scheduled doing)a,
:scheduled => ~w(doing overdued)a,
:doing => ~w(created scheduled done)a
}
use EctoTransit, on: @states, rules: @transitions
end
iex> TODO.can_transit?(:created, :scheduled)
true
iex> TODO.can_transit?(:closed, :created)
falseChange validator name
Set :with to change validator name
use EctoTransit, on: @states, rules: @transitions, with: :should_change?
iex> TODO.should_change?(:created, :scheduled)
trueIntegration with EctoEnum
defmodule Order do
import EctoEnum
defenum State, ~w(created paid in_deliver done)
@transitions %{
~w(created paid in_deliver)a => :done,
:created => :paid,
:paid => :in_deliver
}
use EctoTransit, on: State, rules: @transitions, with: :can_continue?
endValidate changset
defmodule Order do
import EctoEnum
defenum State, ~w(created paid in_deliver done)
@transitions %{
~w(created paid in_deliver)a => :done,
:created => :paid,
:paid => :in_deliver
}
use EctoTransit, on: State, rules: @transitions, with: :can_continue?
use Ecto.Schema
schema "orders" do
field :state, State
end
end
iex> order_changeset |> unsafe_validate_transit(:state)Validate safely
Use lock mechanism to validate safely
changeset
|> unsafe_validate_transit(:state)
|> Ecto.Changeset.optimistic_lock(:lock_version)
# raise if concurrent update happensUse other validator
Set :with to change validator
%Post{}
|> Ecto.Changeset.change()
|> unsafe_validate_transit(:state, with: :should_change?)
## Post.should_change?(from, to)unsafe_validate_transit(:state, with: fn changeset, {from, to} ->
get_field(changeset, :receive_count) == 0 &&
match?({:sent, :recalled}, {from, to})
end
end)unsafe_validate_transit(:state, {Audit, :can_change?, [:update, %{context: nil}]})
## Audit.can_change?(changeset, {from, to}, :update, %{context: nil})FAQ
How to do callback
EctoTransit does not support callbacks like other libs do. The reason callbacks are needed is in OOP ORM frameworks state and change are sealed inside. So to callbacks are useful when you wanna to check or react. But in Ecto, those information are stored directly in a changeset unconcealed.
Just add more validators you could do checking(before).For reaction, db transaction is more explicit and good for maintenance(after).
Validator fail when no change on field
EctoTransit.unsafe_validate_transit/3 requires change happens by default in case other fields change leaving state untouched, which may break state contracts.
So transition validator should not be cuddled with other normal update validator. Or add require: false to skip no change checking if you really know what you are doing.