This package allows to use finite state machine pattern in Ecto. Specify:
- states
- events
- column (optional)
and go:
defmodule User do
use Web, :model
use EctoStateMachine,
states: [:unconfirmed, :confirmed, :blocked, :admin],
events: [
[
name: :confirm,
from: [:unconfirmed],
to: :confirmed,
callback: fn(model) -> Ecto.Changeset.change(model, confirmed_at: Ecto.DateTime.utc) end # yeah you can bring your own code to these functions.
], [
name: :block,
from: [:confirmed, :admin],
to: :blocked
], [
name: :make_admin,
from: [:confirmed],
to: :admin
]
]
schema "users" do
field :state, :string, default: "unconfirmed"
end
end
now you can do:
user = Repo.get_by(User, id: 1)
# Create changeset transition user state to "confirmed". We can make him admin!
confirmed_user = User.confirm(user) # =>
# We can validate ability to change user's state
User.can_confirm?(confirmed_user) # => false
User.can_make_admin?(confirmed_user) # => true
# Create changeset transition user state to "admin"
admin = User.make_admin(confirmed_user)
# Store changeset to the database
Repo.update(admin)
# List all possible states
# If column isn't `:state`, function name will be prefixed. IE,
# for column `:rules` function name will be `rules_states`
User.states # => [:unconfirmed, :confirmed, :blocked, :admin]
# List all possible events
# If column isn't `:state`, function name will be prefixed. IE,
# for column `:rules` function name will be `rules_events`
User.events # => [:confirm, :block, :make_admin]
You can check out whole test/dummy
directory to inspect how to organize sample app.
If available in Hex, the package can be installed as:
-
Add ecto_state_machine to your list of dependencies in
mix.exs
:def deps do [{:ecto_state_machine, "~> 0.1.0"}] end
ecto_state_machine
uses state
database column by default. You can specify
column
option to change it. Like this:
defmodule Dummy.User do
use Dummy.Web, :model
use EctoStateMachine,
column: :rules,
# bla-bla-bla
end
Now your state will be stored into rules
column.
- Install dependencies
mix deps.get
- Setup your
config/test.exs
&config/dev.exs
- Run migrations
mix ecto.migrate
&MIX_ENV=test mix ecto.migrate
- Develop new feature
- Write new tests
- Test it:
mix test
- Open new PR!
- Cover by tests
- Custom db column name
- Validation method for changeset indicates its value in the correct range
- Initial value
- CI
- Add status? methods
- Introduce it at elixir-radar and my blog
- Custom error messages for changeset (with translations by gettext ability)
- Rely on last versions of ecto & elixir
- Write dedicated module instead of requiring everything into the model
- Write bang! methods which are raising exception instead of returning invalid changeset
- Rewrite spaghetti description in README