Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Revert "Add introspection functions to states, events and transitions" #9

Merged
merged 1 commit into from
Jun 29, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Revert "Add introspection functions to states, events and transitions"
  • Loading branch information
norbajunior committed Jun 29, 2022
commit fc84f95ff0ec0508b0ceda7c7d5b3ca24ebe251c
3 changes: 1 addition & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ erl_crash.dump
# Ignore package tarball (built via "mix hex.build").
machinist-*.tar


# Temporary files for e.g. tests
/tmp

/.elixir_ls
31 changes: 1 addition & 30 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ You can install `machinist` by adding it to your list of dependencies in `mix.e
```elixir
def deps do
[
{:machinist, "~> 0.5.1"}
{:machinist, "~> 0.4.1"}
]
end
```
Expand Down Expand Up @@ -225,35 +225,6 @@ defmodule SelectionProcess.V2 do
end
```

## Introspection

To get the list of states, just call:

```elixir
iex> SelectionProcess.V2.__states__()
[:new, :registered, :interview_scheduled, :approved, :reproved, :enrolled]
```

To get the list of events:

```elixir
iex> SelectionProcess.V2.__events__()
["register", "schedule_interview", "approve_interview", "reprove_interview", "enroll"]
```

To get the list of all transitions:

```elixir
iex> SelectionProcess.V2.__transitions__()
[
[from: :new, to: :registered, event: "register"],
[from: :registered, to: :interview_scheduled, event: "schedule_interview"],
[from: :interview_scheduled, to: :approved, event: "approve_interview"],
[from: :interview_scheduled, to: :repproved, event: "reprove_interview"],
[from: :approved, to: :enrolled, event: "enroll"]
]
```

## How does the DSL works?

The use of `transitions` in combination with each `from` statement will be transformed in functions that will be injected into the module that is using `machinist`.
Expand Down
101 changes: 7 additions & 94 deletions lib/machinist.ex
Original file line number Diff line number Diff line change
Expand Up @@ -192,30 +192,6 @@ defmodule Machinist do
end
end

## Introspection

To get the list of states, just call:


iex> SelectionProcess.V2.__states__()
[:new, :registered, :interview_scheduled, :approved, :reproved, :enrolled]

To get the list of events:

iex> SelectionProcess.V2.__events__()
["register", "schedule_interview", "approve_interview", "reprove_interview", "enroll"]

To get the list of all transitions:

iex> SelectionProcess.V2.__transitions__()
[
[from: :new, to: :registered, event: "register"],
[from: :registered, to: :interview_scheduled, event: "schedule_interview"],
[from: :interview_scheduled, to: :approved, event: "approve_interview"],
[from: :interview_scheduled, to: :repproved, event: "reprove_interview"],
[from: :approved, to: :enrolled, event: "enroll"]
]

## How does the DSL works?

The use of `transitions` in combination with each `from` statement will be
Expand Down Expand Up @@ -277,10 +253,6 @@ defmodule Machinist do
@doc false
defmacro __using__(_) do
quote do
Module.register_attribute(__MODULE__, :__states__, accumulate: true, persist: false)
Module.register_attribute(__MODULE__, :__events__, accumulate: true, persist: false)
Module.register_attribute(__MODULE__, :__transitions__, accumulate: true, persist: false)

@__attr__ :state

@behaviour Machinist.Transition
Expand Down Expand Up @@ -395,35 +367,27 @@ defmodule Machinist do
from _state, to: :expired, event: "enrollment_expired"
"""
defmacro from(state, do: {_, _line, to_statements}) do
deftransitions(state, to_statements)
define_transitions(state, to_statements)
end

defmacro from(state, to: new_state, event: event) do
deftransition(state, to: new_state, event: event)
define_transition(state, to: new_state, event: event)
end

@doc false
defp deftransitions(_state, []), do: []
defp define_transitions(_state, []), do: []

@doc false
defp deftransitions(state, [{:to, _line, [new_state, [event: event]]} | transitions]) do
defp define_transitions(state, [{:to, _line, [new_state, [event: event]]} | transitions]) do
[
deftransition(state, to: new_state, event: event)
| deftransitions(state, transitions)
define_transition(state, to: new_state, event: event)
| define_transitions(state, transitions)
]
end

@doc false
defp deftransition(state, to: new_state, event: event) do
ast_states = accumulate_attribute_states(state, new_state)
ast_events = accumulate_attribute_events(event)
ast_transitions = accumulate_attribute_transitions(state, new_state, event)

defp define_transition(state, to: new_state, event: event) do
quote do
unquote(ast_states)
unquote(ast_events)
unquote(ast_transitions)

@impl true
def transit(%@__struct__{@__attr__ => unquote(state)} = resource, event: unquote(event)) do
value = __set_new_state__(resource, unquote(new_state))
Expand All @@ -433,42 +397,6 @@ defmodule Machinist do
end
end

defp accumulate_attribute_transitions(state, new_state, event) do
if not unused_var?(state) do
quote bind_quoted: [state: state, new_state: new_state, event: event] do
@__transitions__ [from: state, to: new_state, event: event]
end
else
quote bind_quoted: [new_state: new_state, event: event] do
@__transitions__ [from: :any, to: new_state, event: event]
end
end
end

defp accumulate_attribute_states(state, new_state) do
if not unused_var?(state) do
quote bind_quoted: [state: state, new_state: new_state] do
if state not in @__states__ do
@__states__ state
end

if new_state not in @__states__ do
@__states__ new_state
end
end
end
end

defp accumulate_attribute_events(event) do
quote bind_quoted: [event: event] do
if event not in @__events__ do
@__events__ event
end
end
end

defp unused_var?(state), do: match?({_, _, nil}, state)

@doc false
defmacro __before_compile__(_) do
quote do
Expand All @@ -477,21 +405,6 @@ defmodule Machinist do
{:error, :not_allowed}
end

def __states__, do: Enum.reverse(@__states__)
def __events__, do: Enum.reverse(@__events__)

def __transitions__ do
Enum.reduce(@__transitions__, [], fn
transition, acc ->
if Keyword.get(transition, :from) == :any do
result = for state <- __states__(), do: Keyword.put(transition, :from, state)
result ++ acc
else
[transition | acc]
end
end)
end

defp __set_new_state__(resource, new_state) do
if is_function(new_state) do
new_state.(resource)
Expand Down
2 changes: 1 addition & 1 deletion mix.exs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
defmodule Machinist.MixProject do
use Mix.Project

@version "0.5.1"
@version "0.4.1"
@repo_url "https://github.com/norbajunior/machinist"

def project do
Expand Down
115 changes: 1 addition & 114 deletions test/machinist_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -20,21 +20,6 @@ defmodule MachinistTest do

assert {:error, :not_allowed} = Example1.transit(step_3, event: "next")
end

test "__states__/0" do
assert Example1.__states__() == [1, 2, 3]
end

test "__events__/0" do
assert Example1.__events__() == ~w(next)
end

test "__transitions__/0" do
assert Example1.__transitions__() == [
[from: 1, to: 2, event: "next"],
[from: 2, to: 3, event: "next"]
]
end
end

describe "an example with custom state attribute" do
Expand All @@ -55,21 +40,6 @@ defmodule MachinistTest do

assert {:error, :not_allowed} = Example2.transit(step_3, event: "next")
end

test "__states__/0" do
assert Example2.__states__() == [1, 2, 3]
end

test "__events__/0" do
assert Example2.__events__() == ~w(next)
end

test "__transitions__/0" do
assert Example2.__transitions__() == [
[from: 1, to: 2, event: "next"],
[from: 2, to: 3, event: "next"]
]
end
end

describe "an example with two modules handling the same struct" do
Expand Down Expand Up @@ -131,29 +101,6 @@ defmodule MachinistTest do
{:error, :not_allowed} = SelectionProcess.V2.transit(enrolled_candidate, event: "enroll")
{:error, :not_allowed} = SelectionProcess.V2.transit(enrolled_candidate, event: "register")
end

test "__states__/0" do
assert SelectionProcess.V1.__states__() == ~w(new registered enrolled)a
assert SelectionProcess.V2.__states__() == ~w(new registered interviewed enrolled)a
end

test "__events__/0" do
assert SelectionProcess.V1.__events__() == ~w(register enroll)
assert SelectionProcess.V2.__events__() == ~w(register interviewed enroll)
end

test "__transitions__/0" do
assert SelectionProcess.V1.__transitions__() == [
[from: :new, to: :registered, event: "register"],
[from: :registered, to: :enrolled, event: "enroll"]
]

assert SelectionProcess.V2.__transitions__() == [
[from: :new, to: :registered, event: "register"],
[from: :registered, to: :interviewed, event: "interviewed"],
[from: :interviewed, to: :enrolled, event: "enroll"]
]
end
end

describe "an example of a module handling a different struct with custom state attr" do
Expand All @@ -173,18 +120,6 @@ defmodule MachinistTest do
{:ok, %User{step: 2} = user_step2} = Example4.transit(%User{}, event: "next")
{:error, :not_allowed} = Example4.transit(user_step2, event: "next")
end

test "__states__/0" do
assert Example4.__states__() == [1, 2]
end

test "__events__/0" do
assert Example4.__events__() == ~w(next)
end

test "__transitions__/0" do
assert Example4.__transitions__() == [[from: 1, to: 2, event: "next"]]
end
end

describe "an example of a transition from any state to a specific one" do
Expand All @@ -199,7 +134,7 @@ defmodule MachinistTest do

from :interview_scheduled do
to(:approved, event: "approve_interview")
to(:reproved, event: "reprove_interview")
to(:repproved, event: "reprove_interview")
end

from(:approved, to: :enrolled, event: "enroll")
Expand All @@ -220,36 +155,6 @@ defmodule MachinistTest do
{:ok, %Example5{state: :application_expired}} =
Example5.transit(%Example5{state: :approved}, event: "application_expired")
end

test "__states__/0" do
assert Example5.__states__() ==
~w(new registered interview_scheduled approved reproved enrolled)a
end

test "__events__/0" do
assert Example5.__events__() ==
~w(register schedule_interview approve_interview reprove_interview enroll application_expired)
end

test "__transitions__/0" do
assert Example5.__transitions__() == [
[from: :new, to: :registered, event: "register"],
[from: :registered, to: :interview_scheduled, event: "schedule_interview"],
[from: :interview_scheduled, to: :approved, event: "approve_interview"],
[from: :interview_scheduled, to: :reproved, event: "reprove_interview"],
[from: :approved, to: :enrolled, event: "enroll"],
[from: :new, to: :application_expired, event: "application_expired"],
[from: :registered, to: :application_expired, event: "application_expired"],
[
from: :interview_scheduled,
to: :application_expired,
event: "application_expired"
],
[from: :approved, to: :application_expired, event: "application_expired"],
[from: :reproved, to: :application_expired, event: "application_expired"],
[from: :enrolled, to: :application_expired, event: "application_expired"]
]
end
end

describe "a example with passing a block of transitions to from" do
Expand All @@ -270,28 +175,10 @@ defmodule MachinistTest do
end

test "all transitions" do
IO.inspect(Example6.__transitions__())
assert {:ok, example} = Example6.transit(%Example6{}, event: "test1")
assert {:ok, %Example6{state: :test2}} = Example6.transit(example, event: "test2")
assert {:ok, %Example6{state: :test3}} = Example6.transit(example, event: "test3")
assert {:ok, %Example6{state: :test4}} = Example6.transit(example, event: "test4")
end

test "__states__/0" do
assert Example6.__states__() == ~w(test test1 test2 test3 test4)a
end

test "__events__/0" do
assert Example6.__events__() == ~w(test1 test2 test3 test4)
end

test "__transitions__/0" do
assert Example6.__transitions__() == [
[from: :test, to: :test1, event: "test1"],
[from: :test1, to: :test2, event: "test2"],
[from: :test1, to: :test3, event: "test3"],
[from: :test1, to: :test4, event: "test4"]
]
end
end
end