Skip to content

cnsa/ecto_as_state_machine

 
 

Repository files navigation

EctoAsStateMachine

Build Status Coverage Status Hex.pm Deps Status

This package allows to use finite state machine pattern in Ecto. Specify:

Installation

If available in Hex, the package can be installed as:

  1. Add ecto_as_state_machine to your list of dependencies in mix.exs:
def deps do
  [{:ecto_as_state_machine, "~> 2.0"}]
end
  1. Ensure ecto_as_state_machine is started before your application:
def application do
  [applications: [:ecto_as_state_machine]]
end

Let's go:

defmodule User do
  use Web, :model

  use EctoAsStateMachine

  easm column: :state,
    initial: :unconfirmed,
    states: [:unconfirmed, :confirmed, :blocked, :admin],
    events: [
      [
        name:     :confirm,
        from:     [:unconfirmed],
        to:       :confirmed,
        callback: fn(model) -> Ecto.Changeset.change(model, confirmed_at: DateTime.utc_now |> DateTime.to_naive) 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
  end
end

now you can run:

user = Repo.get_by(User, id: 1)

new_user_changeset = User.confirm(user)  # => Safe transition user state to "confirmed". We can make him admin!
Repo.update(new_user_changeset) # => Update manually

new_user = User.confirm!(user)  # => Or auto-transition user state to "confirmed". We can make him admin!

User.confirmed?(new_user) # => true
User.admin?(new_user) # => false
User.can_confirm?(new_user)    # => false
User.can_make_admin?(new_user) # => true

new_user = User.make_admin!(new_user)

User.admin?(new_user) # => true

Custom column name

ecto_as_state_machine uses state database column by default. You can specify column option to change it. Or additional column, like this:

defmodule User do
  use Web, :model

  use EctoAsStateMachine

  easm initial: :some,
    # bla-bla-bla

  easm column: :rules,
    # bla-bla-bla
end

Next state

ecto_as_state_machine uses next_state method to try next state. If struct have last state already, it will be not changed. Look how it can be used:

defmodule User do
  use Web, :model

  use EctoAsStateMachine

  easm states: [:unconfirmed, :confirmed, :blocked, :admin],
       initial: :unconfirmed,
       events: [
         [
           name:     :confirm,
           from:     [:unconfirmed],
           to:       :confirmed
         ], [
           name:     :block,
           from:     [:confirmed],
           to:       :blocked
         ], [
           name:     :make_admin,
           from:     [:blocked],
           to:       :admin
         ]
       ],
       # ...
end

user = Repo.get_by(User, id: 1) # state: unconfirmed
new_user = User.next_state(user) # state: confirmed
new_user = User.next_state(new_user) # state: blocked
new_user = User.next_state(new_user) # state: admin
new_user = User.next_state(new_user) # state: admin

Contributions

  1. Clone repo: git clone https://github.com/cnsa/ecto_as_state_machine.git
  2. Open directory cd ecto_as_state_machine
  3. Install dependencies mix deps.get
  4. Add feature
  5. Test it: mix test

Once you've made your additions and mix test passes, go ahead and open a PR!

Roadmap to 2.1

  • Cover by tests
  • Custom db column name
  • Validation method for changeset indicates its value in the correct range
  • Initial value
  • CI
  • Ecto 3.0 support
  • Add status? methods
  • 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

License

MIT

About

State machine pattern for Ecto

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages

  • Elixir 100.0%