From 312285b165b06cd1c09d881c6d8b569fa6aac575 Mon Sep 17 00:00:00 2001 From: Norberto Oliveira Junior Date: Sun, 18 Apr 2021 12:26:45 -0300 Subject: [PATCH] Add new way to set from transitions * Add macro `Machinist.from/2` that expects a state and a block of `to` statements --- README.md | 24 +++++++++++++++++++++++- lib/machinist.ex | 40 ++++++++++++++++++++++++++++++++++++++++ mix.exs | 4 ++-- test/machinist_test.exs | 33 +++++++++++++++++++++++++++++++-- 4 files changed, 96 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 261fb6b..ec00b6e 100644 --- a/README.md +++ b/README.md @@ -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.3.0"} + {:machinist, "~> 0.4.0"} ] end ``` @@ -77,6 +77,28 @@ iex> Door.transit(door_opened, event: "lock") {:error, :not_allowed} ``` +### Group same-state `from` definitions + +In the example above we could group the `from :unlocked` definitions like this: + +```elixir +# ... +transitions do + from :locked, to: :unlocked, event: "unlock" + from :unlocked do + to :locked, event: "lock" + to :opened, event: "open" + end + from :opened, to: :closed, event: "close" + from :closed, to: :opened, event: "open" + from :closed, to: :locked, event: "lock" +end +# ... +``` + +This is an option to a better organization and an increase of readability when having +a large number of `from` definitions with a same state. + ### Setting different attribute name that holds the state By default `machinist` expects the struct being updated holds a `state` attribute, if you hold state in a different attribute, just pass the name as an atom, as follows: diff --git a/lib/machinist.ex b/lib/machinist.ex index e9d89c2..1c14653 100644 --- a/lib/machinist.ex +++ b/lib/machinist.ex @@ -51,6 +51,26 @@ defmodule Machinist do iex> Door.transit(door_opened, event: "lock") iex> {:error, :not_allowed} + ### Group same-state `from` definitions + + In the example above we also could group the `from :unlocked` definitions like this: + + # ... + transitions do + from :locked, to: :unlocked, event: "unlock" + from :unlocked do + to :locked, event: "lock" + to :opened, event: "open" + end + from :opened, to: :closed, event: "close" + from :closed, to: :opened, event: "open" + from :closed, to: :locked, event: "lock" + end + # ... + + This is an option to a better organization and an increase of readability when having + a large number of `from` definitions with a same state. + ### Setting different attribute name that holds the state By default `machinist` expects the struct being updated holds a `state` attribute, @@ -346,7 +366,27 @@ defmodule Machinist do from _state, to: :expired, event: "enrollment_expired" """ + defmacro from(state, do: {_, _line, to_statements}) do + define_transitions(state, to_statements) + end + defmacro from(state, to: new_state, event: event) do + define_transition(state, to: new_state, event: event) + end + + @doc false + defp define_transitions(_state, []), do: [] + + @doc false + defp define_transitions(state, [{:to, _line, [new_state, [event: event]]} | transitions]) do + [ + define_transition(state, to: new_state, event: event) + | define_transitions(state, transitions) + ] + end + + @doc false + defp define_transition(state, to: new_state, event: event) do quote do @impl true def transit(%@__struct__{@__attr__ => unquote(state)} = resource, event: unquote(event)) do diff --git a/mix.exs b/mix.exs index 6c69d35..3b4976d 100644 --- a/mix.exs +++ b/mix.exs @@ -1,13 +1,13 @@ defmodule Machinist.MixProject do use Mix.Project - @version "0.3.0" + @version "0.4.0" @repo_url "https://github.com/norbajunior/machinist" def project do [ app: :machinist, - version: "0.3.0", + version: @version, elixir: "~> 1.6", start_permanent: Mix.env() == :prod, deps: deps(), diff --git a/test/machinist_test.exs b/test/machinist_test.exs index 787eac6..2541a46 100644 --- a/test/machinist_test.exs +++ b/test/machinist_test.exs @@ -131,8 +131,12 @@ defmodule MachinistTest do transitions do 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 :interview_scheduled do + to(:approved, event: "approve_interview") + to(:repproved, event: "reprove_interview") + end + from(:approved, to: :enrolled, event: "enroll") from(_state, to: :application_expired, event: "application_expired") end @@ -152,4 +156,29 @@ defmodule MachinistTest do Example5.transit(%Example5{state: :approved}, event: "application_expired") end end + + describe "a example with passing a block of transitions to from" do + defmodule Example6 do + defstruct state: :test + + use Machinist + + transitions do + from(:test, to: :test1, event: "test1") + + from :test1 do + to(:test2, event: "test2") + to(:test3, event: "test3") + to(:test4, event: "test4") + end + end + end + + test "all transitions" do + 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 + end end