Machinery

Build Status Coverage Status Ebert

Machinery

Machinery is a State Machine library for structs in general that integrates with Pheonix out of the box. It also aims to have (when implemented with Phoenix) an optional build-in GUI that will represent each resource’s state.

Don’t forget to check the Machinery Docs

Installing

The package can be installed by adding machinery to your list of dependencies in mix.exs:

def deps do
  [
    {:machinery, "~> 0.4.1"}
  ]
end

Declaring States

Declare the states as an argment when importing Machinery on the module that will control your states transitions.

Machinery expects a Keyword as argument with two keys states and transitions.

  • states: A List of Atoms representing each state.
  • transitions: A Map for each state and it allowed next state(s).

Example

defmodule YourProject.UserStateMachine do
  use Machinery,
    # The first state declared will be considered
    # the intial state
    states: [:created, :partial, :complete],
    transitions: %{
      created: [:partial, :complete],
      partial: :completed
    }
end

Changing States

To transit a struct into another state, you just need to call Machinery.transition_to/2.

Machinery.transition_to/2

It takes two arguments:

  • struct: The struct you want to transit to another state.
  • next_event: An atom of the next state you want the struct to transition to.

Guard functions, before and after callbacks will be checked automatically.

Machinery.transition_to(your_struct, :next_state)
# {:ok, updated_struct}

Example:

user = Accounts.get_user!(1)
UserStateMachine.transition_to(user, :partial)

Guard functions

Create guard conditions by adding signatures of the guard_transition/2 function, pattern matching the desired state you want to guard.

def guard_transition(struct, :state), do: true

Guard conditions should return a boolean:

  • true: Guard clause will allow the transition.
  • false: Transition won’t be allowed.

Example:

defmodule YourProject.UserStateMachine do
  use Machinery,
    states: [:created, :complete],
    transitions: %{created: :complete}

  # Guard the transition to the :complete state.
  def guard_transition(struct, :complete) do
   Map.get(struct, :missing_fields) == false
  end
end

Before and After callbacks

You can also use before and after callbacks to handle desired side effects and reactions to a specific state transition.

You can just declare before_transition/2 and after_transition/2, pattern matching the desired state you want to.

Make sure Before and After callbacks should return the struct.

# callbacks should always return the struct.
def before_transition(struct, :state), do: struct
def after_transition(struct, :state), do: struct

Example:

defmodule YourProject.UserStateMachine do
  use Machinery,
    states: [:created, :partial, :complete],
    transitions: %{
      created: [:partial, :complete],
      partial: :completed
    }

    def before_transition(struct, :partial) do
      # ... overall desired side effects
      struct
    end

    def after_transition(struct, :completed) do
      # ... overall desired side effects
      struct
    end
end