Eventful

Eventful is a library for anyone who needs a trackable state machine. With transitions and triggers and guards.

Installation

If available in Hex, the package can be installed by adding eventful to your list of dependencies in mix.exs:

def deps do
  [
    {:eventful, "~> 0.2.0"}
  ]
end

Current State

You will need to create a :current_state field in your model

schema "posts" do
  field :current_state, :string, default: "draft"
end

# migration

alter table(:posts) do
  field :current_state, :string, default: "draft", null: false
end

Event Schema

Generally your events table will be used to track events for a specific model you have Let's assume that in this case we have MyApp.Post and MyApp.User as the authenticated user in our app.

We may create something like this.

defmodule MyApp.Post.Event do
  alias MyApp.{
    Post,
    User
  }

  use Eventful,
    parent: {:post, Post},
    actor: {:user, User}
end

Migration

Generate a migration file for your events like this.

defmodule MyApp.Repo.Migrations.CreatePostEvents do
  use Ecto.Migration

  def change do
    create table(:post_events) do
      add(:name, :string, null: false)
      add(:domain, :string, null: false)
      add(:metadata, :map, default: "{}")

      add(
        :post_id,
        references(:posts, on_delete: :restrict),
        null: false
      )

      add(
        :user_id,
        references(:users, on_delete: :restrict),
        null: false
      )

      timestamps()
    end

    create(index(:post_events, [:post_id]))
    create(index(:post_events, [:user_id]))
  end
end

Transitions

The next thing is defining a Transitions module

defmodule MyApp.Post.Transitions do
  alias MyApp.Post

  @behaviour Eventful.Handler

  use Eventful.Transition, repo: MyApp.Repo

  Post
  |> transition(
    [from: "draft", to: "reviewing", via: "review"],
    fn changes -> transit(changes) end)
  )

  Post
  |> transition(
    [from: "reviewing", to: "published", via: "publish"],
    fn changes -> transit(changes) end)
  )
end

Event Handler

You will now need to add the Transitions Handler to your Event module

defmodule MyApp.Post.Event do
  alias MyApp.{
    Post,
    User
  }

  use Eventful,
    parent: {:post, Post},
    actor: {:user, User}

  handle(:transitions, using: Post.Transitions)
end

Transitioning from State to State

MyApp.Post.Event.handle(post, user, %{domain: "transitions", name: "review"})

This will now transition and track your model and also track who did it.

Documentation can be generated with ExDoc and published on HexDocs. Once published, the docs can be found at https://hexdocs.pm/eventful.