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 wildcard transitions. #32

Merged
merged 3 commits into from
Mar 30, 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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 wildcard transitions - [Pull Request](https://github.com/joaomdmoura/machinery/pull/32)

## 0.13.0
- Adding basic auth to Machinery Dashboard - [Pull Request](https://github.com/joaomdmoura/machinery/pull/30)
Expand Down
8 changes: 6 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,14 +84,18 @@ defmodule YourProject.UserStateMachine do
use Machinery,
# The first state declared will be considered
# the initial state
states: ["created", "partial", "complete"],
states: ["created", "partial", "complete", "canceled"],
transitions: %{
"created" => ["partial", "complete"],
"partial" => "completed"
"partial" => "completed",
"*" => "canceled
}
end
```

As you might notice you can use wildcards `"*"` to declare a transition that
can happen from any state to a specific one.

## Changing States

To transit a struct into another state, you just need to
Expand Down
20 changes: 16 additions & 4 deletions lib/machinery/transition.ex
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,10 @@ defmodule Machinery.Transition do
"""
@spec declared_transition?(list, atom, atom) :: boolean
def declared_transition?(transitions, current_state, next_state) do
case Map.fetch(transitions, current_state) do
{:ok, [_|_] = declared_states} -> Enum.member?(declared_states, next_state)
{:ok, declared_state} -> declared_state == next_state
:error -> false
if matches_wildcard?(transitions, next_state) do
true
else
matches_transition?(transitions, current_state, next_state)
end
end

Expand Down Expand Up @@ -59,6 +59,18 @@ defmodule Machinery.Transition do
run_or_fallback(&module.persist/2, &persist_fallback/3, struct, state)
end

defp matches_wildcard?(transitions, next_state) do
matches_transition?(transitions, "*", next_state)
end

defp matches_transition?(transitions, current_state, next_state) do
case Map.fetch(transitions, current_state) do
{:ok, [_|_] = declared_states} -> Enum.member?(declared_states, next_state)
{:ok, declared_state} -> declared_state == next_state
:error -> false
end
end

# Private function that receives a function, a callback,
# a struct and the related state. It tries to execute the function,
# rescue for a couple of specific Exceptions and passes it forward
Expand Down
22 changes: 16 additions & 6 deletions test/machinery/transition_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,22 @@ defmodule MachineryTest.TransitionTest do

test "declared_transition?/3 based on a map of transitions, current and next state" do
transitions = %{
created: [:partial, :completed],
partial: :completed
"created" => ["partial", "completed"],
"partial" => "completed"
}
assert Transition.declared_transition?(transitions, :created, :partial)
assert Transition.declared_transition?(transitions, :created, :completed)
assert Transition.declared_transition?(transitions, :partial, :completed)
refute Transition.declared_transition?(transitions, :partial, :created)
assert Transition.declared_transition?(transitions, "created", "partial")
assert Transition.declared_transition?(transitions, "created", "completed")
assert Transition.declared_transition?(transitions, "partial", "completed")
refute Transition.declared_transition?(transitions, "partial", "created")
end

test "declared_transition?/3 for a declared transition that allows transition for any state" do
transitions = %{
"created" => "completed",
"*" =>"canceled"
}
assert Transition.declared_transition?(transitions, "created", "completed")
assert Transition.declared_transition?(transitions, "created", "canceled")
assert Transition.declared_transition?(transitions, "completed", "canceled")
end
end
Copy link
Contributor

Choose a reason for hiding this comment

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

about this last "end", is it closing a "do" statement before line 5 or something ?

Copy link
Owner Author

Choose a reason for hiding this comment

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

Yup, closing the module itself , the module MachineryTest.TransitionTes :)

10 changes: 10 additions & 0 deletions test/machinery_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,16 @@ defmodule MachineryTest do
assert {:error, "Transition to this state isn't declared."} = Machinery.transition_to(completed_struct, TestStateMachine, "created")
end

test "Wildcard transitions should be valid" do
created_struct = %TestStruct{state: "created", missing_fields: false}
partial_struct = %TestStruct{state: "partial", missing_fields: false}
completed_struct = %TestStruct{state: "completed"}

assert {:ok, %TestStruct{state: "canceled", missing_fields: false}} = Machinery.transition_to(created_struct, TestStateMachine, "canceled")
assert {:ok, %TestStruct{state: "canceled", missing_fields: false}} = Machinery.transition_to(partial_struct, TestStateMachine, "canceled")
assert {:ok, %TestStruct{state: "canceled"}} = Machinery.transition_to(completed_struct, TestStateMachine, "canceled")
end

test "Guard functions should be executed before moving the resource to the next state" do
struct = %TestStruct{state: "created", missing_fields: true}
assert {:error, "Transition not completed, blocked by guard function."} = Machinery.transition_to(struct, TestStateMachineWithGuard, "completed")
Expand Down
5 changes: 3 additions & 2 deletions test/support/test_state_machine.exs
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
defmodule MachineryTest.TestStateMachine do
use Machinery,
states: ["created", "partial", "completed"],
states: ["created", "partial", "completed", "canceled"],
transitions: %{
"created" => ["partial", "completed"],
"partial" => "completed"
"partial" => "completed",
"*" => "canceled"
}

def before_transition(struct, "partial") do
Expand Down