EctoTransit v0.1.0 EctoTransit View Source
Defines transition rules.
When used, the transit expects the :on and :rules as option. The :on should
be a list of avaliable states or an EctoEnum module, and :rules should be a map with transition rules.
You can use state, list of states or wildcard :* in rules.
Optional option :with could be used to change generated function name (default :can_transit?).
It will be handy if you defines two transitions in one module.
States can be any types, though examples are all atom.
iex> 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?(nil, :created)
false
iex> TODO.can_transit?(:scheduled, :overdued)
true
iex> TODO.can_transit?(:unknown, :closed)
false
Use with EctoEnum
EctoTransit is designed for compatibility with EctoEnum, while it works well alone.
Option :on can accept an enum module defined by EctoEnum,
iex> 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?
...> end
iex> Order.can_continue?(:created, :paid)
true
iex> Order.can_continue?(:paid, "in_deliver")
true
Link to this section Summary
Functions
Validate the change on given field follows transition rules.
Link to this section Functions
unsafe_validate_transit(changeset, field, opts \\ [])
View Sourceunsafe_validate_transit(Ecto.Changeset.t(), atom(), keyword()) :: Ecto.Changeset.t()
Validate the change on given field follows transition rules.
This function cannot guarantee transition consistency in concurrency updates thus marked as unsafe. For example, if an change is committed when an check is passed, the follow update might break transition rules, considering an update action of ecto is not conditional.
Extra lock mechanism is required if you want to ensure consistency.
See Ecto.Query.lock/2 or Ecto.Changeset.optimistic_lock/3.
Options
:message- the message on failure, defaults to "cannot transit from %{old} to %{new}":required- if the change on the field is required, defaults to true:with- the function to validate transitions, defaults to :can_transit?. functionfuncan be one of:- atom - called as
apply(mod, fun, [old, new]), whichmodis the module that defines the schama of changeset - capture or anonymous function - called as
apply(fun, [changeset, {old, new}]) - {mod, fun, ex_args} - called as
apply(mod, fun, [changeset, {old, new} | ex_args])
When
requiredis true (by default),oldandnewcould be the same, meaning there's no change on given field.- atom - called as
Examples
use defualt :can_transit?/2:
changeset |> unsafe_validate_transit(:state)
changeset |> unsafe_validate_transit(:state, message: "%{old} -> %{new} is forbidden")
use required: false to skip check if no change:
%Post{state: :published}
|> change() ## no change
|> unsafe_validate_transit(:state, required: false) ## skip check
use Post.can_close?/2:
%Post{state: :published}
|> change(state: :closed)
|> unsafe_validate_transit(:state, with: :can_close?)
use anonymous function like hook:
recall_message_changeset
|> unsafe_validate_transit :state, with: fn changeset, {from, to} ->
get_field(changeset, :receive_count) == 0 && match?({:sent, :recalled}, {from, to})
end
end
use Audit.can_change?(changeset, {from, to}, action, metadata):
sensitive_changeset
|> unsafe_validate_transit(:state, {Audit, :can_change?, [:update, %{context: nil}]})
use Ecto.Query.optimistic_lock/3 to ensure consistency:
changeset
|> unsafe_validate_transit(:state)
|> optimistic_lock(:lock_version) # raise if concurrent update happens