From 656480f1e74525fed5d5384519cf3a03c4983940 Mon Sep 17 00:00:00 2001 From: Cedric Cordenier Date: Fri, 1 Nov 2019 13:19:38 +0000 Subject: [PATCH] [feature] Add callbacks that trigger on transition failure --- lib/statesman/machine.rb | 20 +++++++++++++++- spec/statesman/machine_spec.rb | 42 ++++++++++++++++++++++++++++++++++ 2 files changed, 61 insertions(+), 1 deletion(-) diff --git a/lib/statesman/machine.rb b/lib/statesman/machine.rb index 7edd9fef..11e2ec9d 100644 --- a/lib/statesman/machine.rb +++ b/lib/statesman/machine.rb @@ -48,6 +48,8 @@ def callbacks @callbacks ||= { before: [], after: [], + after_transition_failure: [], + after_guard_failure: [], after_commit: [], guards: [], } @@ -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]) @@ -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 diff --git a/spec/statesman/machine_spec.rb b/spec/statesman/machine_spec.rb index 691bcd59..4805d661 100644 --- a/spec/statesman/machine_spec.rb +++ b/spec/statesman/machine_spec.rb @@ -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) @@ -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