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

Ability to provide extra fields #70

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from 1 commit
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
5 changes: 3 additions & 2 deletions lib/machinery.ex
Original file line number Diff line number Diff line change
Expand Up @@ -92,12 +92,13 @@ defmodule Machinery do
{:ok, %User{state: :completed}}
"""
@spec transition_to(struct, module, String.t()) :: {:ok, struct} | {:error, String.t()}
def transition_to(struct, state_machine_module, next_state) do
def transition_to(struct, state_machine_module, next_state, extra \\ %{}) do
GenServer.call(Machinery.Transitions, {
:run,
struct,
state_machine_module,
next_state
next_state,
extra
}, :infinity)
catch
:exit, error_tuple ->
Expand Down
60 changes: 33 additions & 27 deletions lib/machinery/transition.ex
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,16 @@ defmodule Machinery.Transition do
unless another existing guard condition exists.
This is meant to be for internal use only.
"""
@spec guarded_transition?(module, struct, atom) :: boolean
def guarded_transition?(module, struct, state) do
@spec guarded_transition?(module, struct, atom, map) :: boolean
def guarded_transition?(module, struct, state, extra) do
# IO.puts "guarded_transition?\n#{inspect(module)}\n#{inspect(struct)}\n#{inspect(state)}\n#{inspect(extra)}"
case run_or_fallback(
&module.guard_transition/2,
&module.guard_transition/3,
&guard_transition_fallback/4,
struct,
state,
module._field()
module._field(),
extra
) do
{:error, cause} -> {:error, cause}
_ -> false
Expand All @@ -43,14 +45,15 @@ defmodule Machinery.Transition do
fallback to a boilerplate behaviour.
This is meant to be for internal use only.
"""
@spec before_callbacks(struct, atom, module) :: struct
def before_callbacks(struct, state, module) do
@spec before_callbacks(struct, atom, module, map) :: struct
def before_callbacks(struct, state, module, extra) do
run_or_fallback(
&module.before_transition/2,
&module.before_transition/3,
&callbacks_fallback/4,
struct,
state,
module._field()
module._field(),
extra
)
end

Expand All @@ -59,14 +62,15 @@ defmodule Machinery.Transition do
fallback to a boilerplate behaviour.
This is meant to be for internal use only.
"""
@spec after_callbacks(struct, atom, module) :: struct
def after_callbacks(struct, state, module) do
@spec after_callbacks(struct, atom, module, map) :: struct
def after_callbacks(struct, state, module, extra) do
run_or_fallback(
&module.after_transition/2,
&module.after_transition/3,
&callbacks_fallback/4,
struct,
state,
module._field()
module._field(),
extra
)
end

Expand All @@ -75,23 +79,24 @@ defmodule Machinery.Transition do
changing state.
This is meant to be for internal use only.
"""
@spec persist_struct(struct, atom, module) :: struct
def persist_struct(struct, state, module) do
run_or_fallback(&module.persist/2, &persist_fallback/4, struct, state, module._field())
@spec persist_struct(struct, atom, module, any) :: struct
def persist_struct(struct, state, module, extra) do
run_or_fallback(&module.persist/3, &persist_fallback/4, struct, state, module._field(), extra)
end

@doc """
Function resposible for triggering transitions persistence.
This is meant to be for internal use only.
"""
@spec log_transition(struct, atom, module) :: struct
def log_transition(struct, state, module) do
@spec log_transition(struct, atom, module, map) :: struct
def log_transition(struct, state, module, extra) do
run_or_fallback(
&module.log_transition/2,
&module.log_transition/3,
&log_transition_fallback/4,
struct,
state,
module._field()
module._field(),
extra
)
end

Expand All @@ -112,42 +117,43 @@ defmodule Machinery.Transition do
# rescue for a couple of specific Exceptions and passes it forward
# to the callback, that will re-raise it if not related to
# guard_transition nor before | after call backs
defp run_or_fallback(func, callback, struct, state, field) do
func.(struct, state)
defp run_or_fallback(func, callback, struct, state, field, extra) do
func.(struct, state, extra)
rescue
error in UndefinedFunctionError -> callback.(struct, state, error, field)
error in UndefinedFunctionError ->
callback.(struct, state, error, field)
error in FunctionClauseError -> callback.(struct, state, error, field)
end

defp persist_fallback(struct, state, error, field) do
if error.function == :persist && error.arity == 2 do
if error.function == :persist && error.arity == 3 do
Map.put(struct, field, state)
else
raise error
end
end

defp log_transition_fallback(struct, _state, error, _field) do
if error.function == :log_transition && error.arity == 2 do
if error.function == :log_transition && error.arity == 3 do
struct
else
raise error
end
end

defp callbacks_fallback(struct, _state, error, _field) do
if error.function in [:after_transition, :before_transition] && error.arity == 2 do
if error.function in [:after_transition, :before_transition] && error.arity == 3 do
struct
else
raise error
end
end

# If the exception passed is related to a specific signature of
# guard_transition/2 it will fallback returning true and
# guard_transition/3 it will fallback returning true and
# allwoing the transition, otherwise it will raise the exception.
defp guard_transition_fallback(_struct, _state, error, _field) do
if error.function == :guard_transition && error.arity == 2 do
if error.function == :guard_transition && error.arity == 3 do
true
else
raise error
Expand Down
13 changes: 7 additions & 6 deletions lib/machinery/transitions.ex
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ defmodule Machinery.Transitions do
end

@doc false
def handle_call({:run, struct, state_machine_module, next_state}, _from, states) do
def handle_call({:run, struct, state_machine_module, next_state, extra}, _from, states) do
initial_state = state_machine_module._machinery_initial_state()
transitions = state_machine_module._machinery_transitions()
state_field = state_machine_module._field()
Expand All @@ -35,7 +35,8 @@ defmodule Machinery.Transitions do
# Checking declared transitions and guard functions before
# actually updating the struct and retuning the tuple.
declared_transition? = Transition.declared_transition?(transitions, current_state, next_state)
guarded_transition? = Transition.guarded_transition?(state_machine_module, struct, next_state)
guarded_transition? = Transition.guarded_transition?(state_machine_module, struct, next_state, extra)
# IO.inspect "guarded? #{guarded_transition?}"

response = cond do
!declared_transition? ->
Expand All @@ -46,10 +47,10 @@ defmodule Machinery.Transitions do

true ->
struct = struct
|> Transition.before_callbacks(next_state, state_machine_module)
|> Transition.persist_struct(next_state, state_machine_module)
|> Transition.log_transition(next_state, state_machine_module)
|> Transition.after_callbacks(next_state, state_machine_module)
|> Transition.before_callbacks(next_state, state_machine_module, extra)
|> Transition.persist_struct(next_state, state_machine_module, extra)
|> Transition.log_transition(next_state, state_machine_module, extra)
|> Transition.after_callbacks(next_state, state_machine_module, extra)
{:ok, struct}
end
{:reply, response, states}
Expand Down
34 changes: 17 additions & 17 deletions mix.lock
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
%{
"certifi": {:hex, :certifi, "2.5.1", "867ce347f7c7d78563450a18a6a28a8090331e77fa02380b4a21962a65d36ee5", [:rebar3], [{:parse_trans, "~>3.3", [hex: :parse_trans, repo: "hexpm", optional: false]}], "hexpm"},
"decimal": {:hex, :decimal, "1.8.0", "ca462e0d885f09a1c5a342dbd7c1dcf27ea63548c65a65e67334f4b61803822e", [:mix], [], "hexpm"},
"earmark": {:hex, :earmark, "1.4.2", "3aa0bd23bc4c61cf2f1e5d752d1bb470560a6f8539974f767a38923bb20e1d7f", [:mix], [], "hexpm"},
"ecto": {:hex, :ecto, "3.2.3", "51274df79862845b388733fddcf6f107d0c8c86e27abe7131fa98f8d30761bda", [:mix], [{:decimal, "~> 1.6", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm"},
"ex_doc": {:hex, :ex_doc, "0.21.2", "caca5bc28ed7b3bdc0b662f8afe2bee1eedb5c3cf7b322feeeb7c6ebbde089d6", [:mix], [{:earmark, "~> 1.3.3 or ~> 1.4", [hex: :earmark, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}], "hexpm"},
"excoveralls": {:hex, :excoveralls, "0.12.0", "50e17a1b116fdb7facc2fe127a94db246169f38d7627b391376a0bc418413ce1", [:mix], [{:hackney, "~> 1.0", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm"},
"hackney": {:hex, :hackney, "1.15.2", "07e33c794f8f8964ee86cebec1a8ed88db5070e52e904b8f12209773c1036085", [:rebar3], [{:certifi, "2.5.1", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "6.0.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "1.0.1", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~>1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "1.1.5", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm"},
"idna": {:hex, :idna, "6.0.0", "689c46cbcdf3524c44d5f3dde8001f364cd7608a99556d8fbd8239a5798d4c10", [:rebar3], [{:unicode_util_compat, "0.4.1", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm"},
"jason": {:hex, :jason, "1.1.2", "b03dedea67a99223a2eaf9f1264ce37154564de899fd3d8b9a21b1a6fd64afe7", [:mix], [{:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm"},
"makeup": {:hex, :makeup, "1.0.0", "671df94cf5a594b739ce03b0d0316aa64312cee2574b6a44becb83cd90fb05dc", [:mix], [{:nimble_parsec, "~> 0.5.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm"},
"makeup_elixir": {:hex, :makeup_elixir, "0.14.0", "cf8b7c66ad1cff4c14679698d532f0b5d45a3968ffbcbfd590339cb57742f1ae", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm"},
"metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm"},
"mimerl": {:hex, :mimerl, "1.2.0", "67e2d3f571088d5cfd3e550c383094b47159f3eee8ffa08e64106cdf5e981be3", [:rebar3], [], "hexpm"},
"nimble_parsec": {:hex, :nimble_parsec, "0.5.1", "c90796ecee0289dbb5ad16d3ad06f957b0cd1199769641c961cfe0b97db190e0", [:mix], [], "hexpm"},
"parse_trans": {:hex, :parse_trans, "3.3.0", "09765507a3c7590a784615cfd421d101aec25098d50b89d7aa1d66646bc571c1", [:rebar3], [], "hexpm"},
"ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.5", "6eaf7ad16cb568bb01753dbbd7a95ff8b91c7979482b95f38443fe2c8852a79b", [:make, :mix, :rebar3], [], "hexpm"},
"unicode_util_compat": {:hex, :unicode_util_compat, "0.4.1", "d869e4c68901dd9531385bb0c8c40444ebf624e60b6962d95952775cac5e90cd", [:rebar3], [], "hexpm"},
"certifi": {:hex, :certifi, "2.5.1", "867ce347f7c7d78563450a18a6a28a8090331e77fa02380b4a21962a65d36ee5", [:rebar3], [{:parse_trans, "~>3.3", [hex: :parse_trans, repo: "hexpm", optional: false]}], "hexpm", "805abd97539caf89ec6d4732c91e62ba9da0cda51ac462380bbd28ee697a8c42"},
"decimal": {:hex, :decimal, "1.8.0", "ca462e0d885f09a1c5a342dbd7c1dcf27ea63548c65a65e67334f4b61803822e", [:mix], [], "hexpm", "52694ef56e60108e5012f8af9673874c66ed58ac1c4fae9b5b7ded31786663f5"},
"earmark": {:hex, :earmark, "1.4.2", "3aa0bd23bc4c61cf2f1e5d752d1bb470560a6f8539974f767a38923bb20e1d7f", [:mix], [], "hexpm", "5e8806285d8a3a8999bd38e4a73c58d28534c856bc38c44818e5ba85bbda16fb"},
"ecto": {:hex, :ecto, "3.2.3", "51274df79862845b388733fddcf6f107d0c8c86e27abe7131fa98f8d30761bda", [:mix], [{:decimal, "~> 1.6", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "7f3ccc2cd19a0239819df5a290fb0e61a5e92798d5fef95e49617464f8089c51"},
"ex_doc": {:hex, :ex_doc, "0.21.2", "caca5bc28ed7b3bdc0b662f8afe2bee1eedb5c3cf7b322feeeb7c6ebbde089d6", [:mix], [{:earmark, "~> 1.3.3 or ~> 1.4", [hex: :earmark, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}], "hexpm", "f1155337ae17ff7a1255217b4c1ceefcd1860b7ceb1a1874031e7a861b052e39"},
"excoveralls": {:hex, :excoveralls, "0.12.0", "50e17a1b116fdb7facc2fe127a94db246169f38d7627b391376a0bc418413ce1", [:mix], [{:hackney, "~> 1.0", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "1f70c34b462719a519e9d8be0b5f81a49557d8227a2c729d252c3cf4fc84d9cd"},
"hackney": {:hex, :hackney, "1.15.2", "07e33c794f8f8964ee86cebec1a8ed88db5070e52e904b8f12209773c1036085", [:rebar3], [{:certifi, "2.5.1", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "6.0.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "1.0.1", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~>1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "1.1.5", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm", "e0100f8ef7d1124222c11ad362c857d3df7cb5f4204054f9f0f4a728666591fc"},
"idna": {:hex, :idna, "6.0.0", "689c46cbcdf3524c44d5f3dde8001f364cd7608a99556d8fbd8239a5798d4c10", [:rebar3], [{:unicode_util_compat, "0.4.1", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "4bdd305eb64e18b0273864920695cb18d7a2021f31a11b9c5fbcd9a253f936e2"},
"jason": {:hex, :jason, "1.1.2", "b03dedea67a99223a2eaf9f1264ce37154564de899fd3d8b9a21b1a6fd64afe7", [:mix], [{:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "fdf843bca858203ae1de16da2ee206f53416bbda5dc8c9e78f43243de4bc3afe"},
"makeup": {:hex, :makeup, "1.0.0", "671df94cf5a594b739ce03b0d0316aa64312cee2574b6a44becb83cd90fb05dc", [:mix], [{:nimble_parsec, "~> 0.5.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "a10c6eb62cca416019663129699769f0c2ccf39428b3bb3c0cb38c718a0c186d"},
"makeup_elixir": {:hex, :makeup_elixir, "0.14.0", "cf8b7c66ad1cff4c14679698d532f0b5d45a3968ffbcbfd590339cb57742f1ae", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "d4b316c7222a85bbaa2fd7c6e90e37e953257ad196dc229505137c5e505e9eff"},
"metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"},
"mimerl": {:hex, :mimerl, "1.2.0", "67e2d3f571088d5cfd3e550c383094b47159f3eee8ffa08e64106cdf5e981be3", [:rebar3], [], "hexpm", "f278585650aa581986264638ebf698f8bb19df297f66ad91b18910dfc6e19323"},
"nimble_parsec": {:hex, :nimble_parsec, "0.5.1", "c90796ecee0289dbb5ad16d3ad06f957b0cd1199769641c961cfe0b97db190e0", [:mix], [], "hexpm", "00e3ebdc821fb3a36957320d49e8f4bfa310d73ea31c90e5f925dc75e030da8f"},
"parse_trans": {:hex, :parse_trans, "3.3.0", "09765507a3c7590a784615cfd421d101aec25098d50b89d7aa1d66646bc571c1", [:rebar3], [], "hexpm", "17ef63abde837ad30680ea7f857dd9e7ced9476cdd7b0394432af4bfc241b960"},
"ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.5", "6eaf7ad16cb568bb01753dbbd7a95ff8b91c7979482b95f38443fe2c8852a79b", [:make, :mix, :rebar3], [], "hexpm", "13104d7897e38ed7f044c4de953a6c28597d1c952075eb2e328bc6d6f2bfc496"},
"unicode_util_compat": {:hex, :unicode_util_compat, "0.4.1", "d869e4c68901dd9531385bb0c8c40444ebf624e60b6962d95952775cac5e90cd", [:rebar3], [], "hexpm", "1d1848c40487cdb0b30e8ed975e34e025860c02e419cb615d255849f3427439d"},
}
6 changes: 3 additions & 3 deletions test/support/test_state_machine.exs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ defmodule MachineryTest.TestStateMachine do
"*" => "canceled"
}

def before_transition(struct, "partial") do
def before_transition(struct, "partial", _extra) do
# Code to simulate and force an exception inside a
# guard function.
if Map.get(struct, :force_exception) do
Expand All @@ -18,11 +18,11 @@ defmodule MachineryTest.TestStateMachine do
Map.put(struct, :missing_fields, true)
end

def after_transition(struct, "completed") do
def after_transition(struct, "completed", _extra) do
Map.put(struct, :missing_fields, false)
end

def persist(struct, next_state) do
def persist(struct, next_state, _extra) do
# Code to simulate and force an exception inside a
# guard function.
if Map.get(struct, :force_exception) do
Expand Down
5 changes: 3 additions & 2 deletions test/support/test_state_machine_with_guard.exs
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,11 @@ defmodule MachineryTest.TestStateMachineWithGuard do
"partial" => "completed"
}

def guard_transition(struct, "completed") do
def guard_transition(struct, "completed", _extra) do
# Code to simulate and force an exception inside a
# guard function.
if Map.get(struct, :force_exception) do
IO.inspect "raising"
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing inspect 😄

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

whoops! sorry 😄

Machinery.non_existing_function_should_raise_error()
end

Expand All @@ -21,7 +22,7 @@ defmodule MachineryTest.TestStateMachineWithGuard do
end
end

def log_transition(struct, _next_state) do
def log_transition(struct, _next_state, _extra) do
# Log transition here
if Map.get(struct, :force_exception) do
Machinery.non_existing_function_should_raise_error()
Expand Down