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

Adding support for logging transitions #33

Merged
merged 1 commit into from Apr 11, 2018
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
@@ -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
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
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
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
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
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