Skip to content

Commit

Permalink
Merge branch 'release/2.1.0'
Browse files Browse the repository at this point in the history
  • Loading branch information
zacksiri committed Dec 21, 2022
2 parents 6015802 + a90e585 commit 9bbfea8
Show file tree
Hide file tree
Showing 7 changed files with 68 additions and 20 deletions.
29 changes: 15 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
# Eventful

![](https://github.com/zacksiri/eventful/workflows/Elixir%20CI/badge.svg)
![](https://github.com/zacksiri/eventful/workflows/Elixir%20CI/badge.svg) [![Hex.pm](https://img.shields.io/hexpm/v/eventful.svg)](https://hex.pm/packages/eventful) [![Documentation](https://img.shields.io/badge/documentation-gray)](https://hexdocs.pm/eventful)

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

## Installation

If [available in Hex](https://hex.pm/docs/publish), the package can be installed
by adding `eventful` to your list of dependencies in `mix.exs`:
You can add `eventful` to your list of dependencies in `mix.exs`:

```elixir
def deps do
Expand All @@ -21,7 +20,7 @@ Eventful is a state machine library with an audit trail for your schemas. You ca

In the following we will use a blogging app as an example. Let's imagine you had a schema like the following to store your blog post.

```
```elixir
defmodule MyApp.Post do
use Ecto.Schema
import Ecto.Changeset
Expand All @@ -42,7 +41,7 @@ end

Let's imagine you want the ability to track the `state` of this post. You may have a collaboration feature where posts can be put into `draft` or `published` state, moreover you also want to track who did the transition. Let's assume you have a `User` schema of some kind. You could define an `Event` module like the following:

```
```elixir
defmodule MyApp.Post.UserEvent do
alias MyApp.{
Post,
Expand All @@ -60,7 +59,7 @@ end

To make this work you'll also need to add a migration.

```
```elixir
defmodule MyApp.Repo.Migrations.CreatePostUserEvents do
use Ecto.Migration

Expand Down Expand Up @@ -94,7 +93,7 @@ end
## State Machine
Next you'll need to define your `Transitions` this will allow you to define which states the post can transition to.

```
```elixir
defmodule MyApp.Post.Transitions do
use Eventful.Transition, repo: MyApp.Repo

Expand All @@ -116,7 +115,7 @@ end

Next you'll need to add some field to your `Post` schema which will be used to track the transitions. In this case let's add `:current_state` as the field and also define how the field is governed.

```
```elixir
defmodule MyApp.Post do
# ...

Expand All @@ -141,7 +140,7 @@ end

Also be sure to define the handler in your `UserEvent` module

```
```elixir
defmodule MyApp.Post.UserEvent do
alias MyApp.{
Post,
Expand All @@ -159,7 +158,7 @@ end

You'll also need to add a migration for the post. You can use `:string` or if you prefer `:citext` for your `:current_state` field.

```
```elixir
defmodule MyApp.Repo.Migrations.AddCurrentStateToPosts do
use Ecto.Migration

Expand All @@ -177,11 +176,13 @@ end

That's it! That's how your set up your first auditable state machine on your schema. You can how transition the post from state to state.

```
```elixir
{:ok, transition} =
MyApp.Post.UserEvent.handle(post, user, %{domain: "transitions", event_name: "publish"})
```

Documentation can be generated with [ExDoc](https://github.com/elixir-lang/ex_doc)
and published on [HexDocs](https://hexdocs.pm). Once published, the docs can
be found at [https://hexdocs.pm/eventful](https://hexdocs.pm/eventful).
## Copyright and License

Copyright (c) 2022, Zack Siri.

Eventful source code is licensed under the [MIT License](LICENSE.md).
2 changes: 2 additions & 0 deletions guides/introduction/getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,8 @@ defmodule MyApp.Repo.Migrations.AddCurrentStateToPosts do
end
```

## Transitioning the State

That's it! That's how your set up your first auditable state machine on your schema. You can how transition the post from state to state.

```
Expand Down
20 changes: 16 additions & 4 deletions lib/eventful/transitable.ex
Original file line number Diff line number Diff line change
Expand Up @@ -43,15 +43,18 @@ defmodule Eventful.Transitable do
alias __MODULE__.Event
alias __MODULE__.Transitions
alias __MODULE__.Visibilities
# You can optionally use locks
Transitions
|> governs(:current_state, on: Event)
|> governs(:current_state, on: Event, lock: :current_state_versions)
Visibilities
|> governs(:visibility, on: Event)
schema "posts" do
field :current_state, :string, default: "created"
field :current_state_version, :integer, default: 0
field :visibility, :string, default: "private"
end
end
Expand All @@ -78,7 +81,14 @@ defmodule Eventful.Transitable do

@governors
|> Enum.reduce(changeset, fn g, acc ->
validate_inclusion(acc, g.governs, g.module.valid_states())
changeset =
validate_inclusion(acc, g.governs, g.module.valid_states())

if lock_field = g.lock do
optimistic_lock(changeset, lock_field)
else
changeset
end
end)
end
end
Expand All @@ -92,12 +102,14 @@ defmodule Eventful.Transitable do
"""
defmacro governs(module, field, options) do
on = Keyword.fetch!(options, :on)
lock = Keyword.get(options, :lock)

quote do
@governors %{
module: unquote(module),
governs: unquote(field),
via: unquote(on)
via: unquote(on),
lock: unquote(lock)
}
end
end
Expand Down
2 changes: 1 addition & 1 deletion mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ defmodule Eventful.MixProject do
@moduledoc false
use Mix.Project

@version "2.0.1"
@version "2.1.0"

def project do
[
Expand Down
21 changes: 21 additions & 0 deletions test/eventful/transitions_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,27 @@ defmodule Eventful.TransitionsTest do
end
end

describe "transition with lock" do
test "failed because of stale entry", %{model: model, actor: actor} do
assert {:ok, %{resource: transitioned_model}} =
Model.Event.handle(model, actor, %{
domain: "transitions",
name: "process",
comment: nil
})

assert transitioned_model.current_state_version == 1

assert_raise(Ecto.StaleEntryError, fn ->
Model.Event.handle(model, actor, %{
domain: "transitions",
name: "process",
comment: nil
})
end)
end
end

describe "transitions" do
test "get all transitions", %{model: _model} do
assert Enum.count(Model.Transitions.all()) == 2
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
defmodule Eventful.Test.Repo.Migrations.AddCurrentStateVersionsOnModels do
use Ecto.Migration

def change do
alter table(:models) do
add(:current_state_version, :integer, null: false, default: 0)
end
end
end
5 changes: 4 additions & 1 deletion test/support/model.ex
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ defmodule Eventful.Test.Model do
alias __MODULE__.InternalEvent

Transitions
|> governs(:current_state, on: Event)
|> governs(:current_state, on: Event, lock: :current_state_version)

Publishings
|> governs(:publish_state, on: Event)
Expand All @@ -25,7 +25,10 @@ defmodule Eventful.Test.Model do

schema "models" do
field(:publish_state, :string, default: "draft")

field(:current_state, :string, default: "created")
field(:current_state_version, :integer, default: 0)

field(:internal_state, :string, default: "created")

has_many :events, __MODULE__.Event
Expand Down

0 comments on commit 9bbfea8

Please sign in to comment.