Skip to content

Commit

Permalink
[feature] Add callbacks that trigger on transition failure (#366)
Browse files Browse the repository at this point in the history
[feature] Add callbacks that trigger on transition failure

Co-authored-by: Cedric Cordenier <cedric.cordenier@coinbase.com>
  • Loading branch information
danwakefield and Cedric Cordenier authored Nov 5, 2019
2 parents 98a22bf + 656480f commit befa56b
Show file tree
Hide file tree
Showing 2 changed files with 61 additions and 1 deletion.
20 changes: 19 additions & 1 deletion lib/statesman/machine.rb
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ def callbacks
@callbacks ||= {
before: [],
after: [],
after_transition_failure: [],
after_guard_failure: [],
after_commit: [],
guards: [],
}
Expand Down Expand Up @@ -83,6 +85,16 @@ def after_transition(options = { after_commit: false }, &block)
from: options[:from], to: options[:to], &block)
end

def after_transition_failure(options = {}, &block)
add_callback(callback_type: :after_transition_failure, callback_class: Callback,
from: options[:from], to: options[:to], &block)
end

def after_guard_failure(options = {}, &block)
add_callback(callback_type: :after_guard_failure, callback_class: Callback,
from: options[:from], to: options[:to], &block)
end

def validate_callback_condition(options = { from: nil, to: nil })
from = to_s_or_nil(options[:from])
to = array_to_s_or_nil(options[:to])
Expand Down Expand Up @@ -219,9 +231,15 @@ def transition_to!(new_state, metadata = {})
@storage_adapter.create(initial_state, new_state, metadata)

true
rescue TransitionFailedError
execute(:after_transition_failure, initial_state, new_state)
raise
rescue GuardFailedError
execute(:after_guard_failure, initial_state, new_state)
raise
end

def execute(phase, initial_state, new_state, transition)
def execute(phase, initial_state, new_state, transition = nil)
callbacks = callbacks_for(phase, from: initial_state, to: new_state)
callbacks.each { |cb| cb.call(@object, transition) }
end
Expand Down
42 changes: 42 additions & 0 deletions spec/statesman/machine_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,16 @@
it_behaves_like "a callback store", :guard_transition, :guards
end

describe ".after_transition_failure" do
it_behaves_like "a callback store",
:after_transition_failure,
:after_transition_failure
end

describe ".after_guard_failure" do
it_behaves_like "a callback store", :after_guard_failure, :after_guard_failure
end

describe "#initialize" do
it "accepts an object to manipulate" do
machine_instance = machine.new(my_model)
Expand Down Expand Up @@ -672,6 +682,38 @@ def after_initialize; end
expect { instance.transition_to!(:y) }.
to raise_error(Statesman::GuardFailedError)
end

context "and a guard failed callback defined" do
let(:guard_failure_result) { true }
let(:guard_failure_cb) { ->(*_args) { guard_failure_result } }

before { machine.after_guard_failure(from: :x, to: :y, &guard_failure_cb) }

it "calls the failure callback" do
expect(guard_failure_cb).to receive(:call).once.with(
my_model, nil
).and_return(guard_failure_result)
expect { instance.transition_to!(:y) }.
to raise_error(Statesman::GuardFailedError)
end
end
end
end

context "with a transition failed callback" do
let(:result) { true }
let(:transition_failed_cb) { ->(*_args) { result } }
let(:instance) { machine.new(my_model) }

before do
machine.after_transition_failure(&transition_failed_cb)
end

it "raises and exception and calls the callback" do
expect(transition_failed_cb).to receive(:call).once.
with(my_model, nil).and_return(true)
expect { instance.transition_to!(:z) }.
to raise_error(Statesman::TransitionFailedError)
end
end
end
Expand Down

0 comments on commit befa56b

Please sign in to comment.