Skip to content

Commit

Permalink
Merge pull request #33 from joaomdmoura/store-transitions
Browse files Browse the repository at this point in the history
Adding support for logging transitions
  • Loading branch information
joaomdmoura committed Apr 11, 2018
2 parents 5448c53 + eaf183b commit 538d47e
Show file tree
Hide file tree
Showing 6 changed files with 73 additions and 1 deletion.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# ChangeLog

## WIP Version
- Adding support for transitions logging - [Pull Request](https://github.com/joaomdmoura/machinery/pull/33)

## 0.14.0
- Adding support for wildcard transitions - [Pull Request](https://github.com/joaomdmoura/machinery/pull/32)
Expand Down
31 changes: 31 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ Don't forget to check the [Machinery Docs](https://hexdocs.pm/machinery)
- [Declaring States](#declaring-states)
- [Changing States](#changing-states)
- [Persist State](#persist-state)
- [Logging Transitions](#logging-transitions)
- [Enable Dashboard with Phoenix](#enable-dashboard-wiht-phoenix)
- [Guard Functions](#guard-functions)
- [Before and After Callbacks](#before-and-after-callbacks)
Expand Down Expand Up @@ -151,6 +152,36 @@ defmodule YourProject.UserStateMachine do
end
```
## Logging Transitions
To log/persist the transitions itself Machinery provides a callback
`log_transitions/2` that will be called on every transition.
It will receive the unchanged `struct` as the first argument and a `string` of
the next state as the second one, after every state transition.
This function will be called between the before and after transition callbacks
and after the persist function.
**`log_transition/2` should always return the updated struct.**
### Example:
```elixir
defmodule YourProject.UserStateMachine do
alias YourProject.Accounts
use Machinery,
states: ["created", "complete"],
transitions: %{"created" => "complete"}
def log_transition(struct, _next_state) do
# Log transition here, save on the DB or whatever.
# ...
# Return the struct.
struct
end
end
```
## Enable Dashboard with Phoenix
In case you're using Phoenix and want visual dashboard representing your state
Expand Down
19 changes: 18 additions & 1 deletion lib/machinery/transition.ex
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ defmodule Machinery.Transition do
end

@doc """
This functions will try to trigger persistence, if declared, to the struct
This function will try to trigger persistence, if declared, to the struct
changing state.
This is meant to be for internal use only.
"""
Expand All @@ -59,6 +59,15 @@ defmodule Machinery.Transition do
run_or_fallback(&module.persist/2, &persist_fallback/3, struct, state)
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
run_or_fallback(&module.log_transition/2, &log_transition_fallback/3, struct, state)
end

defp matches_wildcard?(transitions, next_state) do
matches_transition?(transitions, "*", next_state)
end
Expand Down Expand Up @@ -91,6 +100,14 @@ defmodule Machinery.Transition do
end
end

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

defp callbacks_fallback(struct, _state, error) do
if error.function in [:after_transition, :before_transition] && error.arity == 2 do
struct
Expand Down
1 change: 1 addition & 0 deletions lib/machinery/transitions.ex
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ defmodule Machinery.Transitions do
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)
{:ok, struct}
end
Expand Down
13 changes: 13 additions & 0 deletions test/machinery_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,19 @@ defmodule MachineryTest do
end
end

test "Transition log function should be called after the transition" do
struct = %TestStruct{state: "created"}
assert {:ok, _} = Machinery.transition_to(struct, TestStateMachineWithGuard, "partial")
end

@tag :capture_log
test "Transition log function should still reaise errors if not related to the existence of persist/1 method" do
wrong_struct = %TestStruct{state: "created", force_exception: true}
assert_raise UndefinedFunctionError, fn() ->
Machinery.transition_to(wrong_struct, TestStateMachineWithGuard, "partial")
end
end

@tag :capture_log
test "Machinery.Transitions GenServer should be started under the Machinery.Supervisor" do
transitions_pid = Process.whereis(Machinery.Transitions)
Expand Down
9 changes: 9 additions & 0 deletions test/support/test_state_machine_with_guard.exs
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,13 @@ defmodule MachineryTest.TestStateMachineWithGuard do

Map.get(struct, :missing_fields) == false
end

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

struct
end
end

0 comments on commit 538d47e

Please sign in to comment.