Giter VIP home page Giter VIP logo

eventful's Introduction

Eventful

Hex.pm Documentation

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

Installation

You can add eventful to your list of dependencies in mix.exs:

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

Eventful is a state machine library with an audit trail for your schemas. You can attach a state machine to any schema in your application.

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.

defmodule MyApp.Post do
  use Ecto.Schema
  import Ecto.Changeset

  schema "posts" do
    field :title, :string
    field :content, :string
  end

  def changeset(resource, attrs) do
    resource
    |> cast(attrs, [:title, :content])
  end
end

Event Schema

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:

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

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

Migration

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

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

  def change do
    create table(:post_user_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

State Machine

Next you'll need to define your Transitions this will allow you to define which states the post can transition to.

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

  @behaviour Eventful.Handler

  alias MyApp.Post

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

  Post
  |> transition([from: "published", to: "draft", via: "drafting", fn changes ->
    transit(changes)
  end)
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.

defmodule MyApp.Post do
  # ...

  use Eventful.Transitable

  alias __MODULE__.UserEvent
  alias __MODULE__.Transitions

  Transitions
  |> governs(:current_state, on: UserEvent)

  schema "posts" do
    field :title, :string
    field :content, :string

    field :current_state, :string, default: "draft"
  end

  # ...
end

Also be sure to define the handler in your UserEvent module

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

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

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

defimpl Eventful.Transit, for: MyApp.Post do
  alias MyApp.Post.UserEvent

  def create(resource, actor, event_name, options \\ []) do
    comment = Keyword.get(options, :comment)
    parameters = Keyword.get(options, :parameters)

    UserEvent.handle(post, user, %{
      domain: "transitions", 
      event_name: event_name,
      comment: comment,
      parameters: parameters
    })
  end
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.

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

  def change do
    alter table(:posts) do
      add(:current_state, :citext, default: "draft", null: false)
    end

    create(index(:posts, [:current_state]))
  end
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.

{:ok, transition} = Eventful.Transit.perform(post, user, "publish")

Copyright and License

Copyright (c) 2022, Zack Siri.

Eventful source code is licensed under the MIT License.

eventful's People

Contributors

zacksiri avatar mathieuprog avatar blisscs avatar

Stargazers

Niranjan Anandkumar avatar Adrian-Paul Carrières avatar Noah Betzen avatar Juri Hahn avatar  avatar James Herdman avatar Patrick Lafleur avatar Gianni Chiappetta avatar  avatar Pavel Tsurbeleu avatar lidashuang avatar Erik Nilsen avatar Aayush Sahu avatar Alessandro Iob avatar Bill Gloff avatar Mete Can Eriş avatar Prashant Pradhan avatar Josh Dollinger avatar Brian Howenstein avatar Felix Bruckmeier avatar Ian Smith avatar Dylan Jones avatar Rod Gaither avatar Andy Duong avatar Seungjin Kim avatar Matteo Costantini avatar Florian Parga avatar  avatar Felipe Menegazzi avatar almokhtar avatar

Watchers

 avatar James Cloos avatar  avatar

Forkers

blisscs

eventful's Issues

Add optimistic lock as optional

Would be good to have optimistic locking on the changeset. There are chances of multiple objects transitioning objects in memory. Optimistic locks solve this problem.

List of possible events

This is generally handy for example if we want to render the list of possible events from the entity's current_state

Ability to return Multi

Sometimes we want transitions as a part of a transaction, it would be nice to be able to return a multi that users can use to integrate into their transaction call.

Consolidate error

Currently eventful will spit out tuple error with many items. We need to consolidate this to {:error, error_message} since it's easier to handle this way. For context see upmaru/uplink#50

Ability to pass parameter in event

We should have the ability to pass arbitrary params as json object. The field should just be simple json field that can store a set of params that can be called and used later.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.