mirror of
https://github.com/aasm/aasm
synced 2023-03-27 23:22:41 -04:00
attempt add global before transitions
coverage for not calling the transition callbacks complete rething, support before after all events, error all events, and ensure
This commit is contained in:
parent
a31b02cc2b
commit
e64a84f71f
6 changed files with 254 additions and 61 deletions
|
@ -146,6 +146,7 @@ Here you can see a list of all possible callbacks, together with their order of
|
|||
|
||||
```ruby
|
||||
begin
|
||||
event before_all_events
|
||||
event before
|
||||
event guards
|
||||
transition guards
|
||||
|
@ -160,8 +161,13 @@ begin
|
|||
old_state after_exit
|
||||
new_state after_enter
|
||||
event after
|
||||
event after_all_events
|
||||
rescue
|
||||
event error
|
||||
event error_on_all_events
|
||||
ensure
|
||||
event ensure
|
||||
event ensure_on_all_events
|
||||
end
|
||||
```
|
||||
|
||||
|
|
|
@ -75,6 +75,12 @@ private
|
|||
begin
|
||||
old_state = aasm(state_machine_name).state_object_for_name(aasm(state_machine_name).current_state)
|
||||
|
||||
event.fire_global_callbacks(
|
||||
:before_all_events,
|
||||
self,
|
||||
*process_args(event, aasm(state_machine_name).current_state, *args)
|
||||
)
|
||||
|
||||
# new event before callback
|
||||
event.fire_callbacks(
|
||||
:before,
|
||||
|
@ -97,7 +103,12 @@ private
|
|||
aasm_failed(state_machine_name, event_name, old_state)
|
||||
end
|
||||
rescue StandardError => e
|
||||
event.fire_callbacks(:error, self, e, *process_args(event, aasm(state_machine_name).current_state, *args)) || raise(e)
|
||||
event.fire_callbacks(:error, self, e, *process_args(event, aasm(state_machine_name).current_state, *args)) ||
|
||||
event.fire_global_callbacks(:error_on_all_events, self, e, *process_args(event, aasm(state_machine_name).current_state, *args)) ||
|
||||
raise(e)
|
||||
ensure
|
||||
event.fire_callbacks(:ensure, self, *process_args(event, aasm(state_machine_name).current_state, *args))
|
||||
event.fire_global_callbacks(:ensure_on_all_events, self, *process_args(event, aasm(state_machine_name).current_state, *args))
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -134,6 +145,11 @@ private
|
|||
self,
|
||||
*process_args(event, old_state.name, *args)
|
||||
)
|
||||
event.fire_global_callbacks(
|
||||
:after_all_events,
|
||||
self,
|
||||
*process_args(event, old_state.name, *args)
|
||||
)
|
||||
|
||||
self.aasm_event_fired(event.name, old_state.name, aasm(state_machine_name).current_state) if self.respond_to?(:aasm_event_fired)
|
||||
else
|
||||
|
|
|
@ -113,6 +113,22 @@ module AASM
|
|||
@state_machine.add_global_callbacks(:after_all_transitions, *callbacks, &block)
|
||||
end
|
||||
|
||||
def before_all_events(*callbacks, &block)
|
||||
@state_machine.add_global_callbacks(:before_all_events, *callbacks, &block)
|
||||
end
|
||||
|
||||
def after_all_events(*callbacks, &block)
|
||||
@state_machine.add_global_callbacks(:after_all_events, *callbacks, &block)
|
||||
end
|
||||
|
||||
def error_on_all_events(*callbacks, &block)
|
||||
@state_machine.add_global_callbacks(:error_on_all_events, *callbacks, &block)
|
||||
end
|
||||
|
||||
def ensure_on_all_events(*callbacks, &block)
|
||||
@state_machine.add_global_callbacks(:ensure_on_all_events, *callbacks, &block)
|
||||
end
|
||||
|
||||
def states
|
||||
@state_machine.states
|
||||
end
|
||||
|
|
|
@ -13,7 +13,7 @@ module AASM::Core
|
|||
|
||||
# from aasm4
|
||||
@options = options # QUESTION: .dup ?
|
||||
add_options_from_dsl(@options, [:after, :before, :error, :success, :after_commit], &block) if block
|
||||
add_options_from_dsl(@options, [:after, :before, :error, :success, :after_commit, :ensure], &block) if block
|
||||
end
|
||||
|
||||
# a neutered version of fire - it doesn't actually fire the event, it just
|
||||
|
@ -43,6 +43,10 @@ module AASM::Core
|
|||
@transitions.select { |t| t.to == state }
|
||||
end
|
||||
|
||||
def fire_global_callbacks(callback_name, record, *args)
|
||||
invoke_callbacks(state_machine.global_callbacks[callback_name], record, args)
|
||||
end
|
||||
|
||||
def fire_callbacks(callback_name, record, *args)
|
||||
# strip out the first element in args if it's a valid to_state
|
||||
# #given where we're coming from, this condition implies args not empty
|
||||
|
@ -145,6 +149,5 @@ module AASM::Core
|
|||
false
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end # AASM
|
||||
|
|
|
@ -18,7 +18,10 @@ module Callbacks
|
|||
end
|
||||
|
||||
aasm do
|
||||
after_all_transitions :after_all_transitions
|
||||
before_all_events :before_all_events
|
||||
after_all_events :after_all_events
|
||||
ensure_on_all_events :ensure_on_all_events
|
||||
after_all_transitions :after_all_transitions
|
||||
|
||||
state :open, :initial => true,
|
||||
:before_enter => :before_enter_open,
|
||||
|
@ -36,11 +39,11 @@ module Callbacks
|
|||
:exit => :exit_closed,
|
||||
:after_exit => :after_exit_closed
|
||||
|
||||
event :close, :before => :before_event, :after => :after_event, :guard => :event_guard do
|
||||
event :close, :before => :before_event, :after => :after_event, :guard => :event_guard, :ensure => :ensure_event do
|
||||
transitions :to => :closed, :from => [:open], :guard => :transition_guard, :after => :after_transition
|
||||
end
|
||||
|
||||
event :open, :before => :before_event, :after => :after_event do
|
||||
event :open, :before => :before_event, :after => :after_event do
|
||||
transitions :to => :open, :from => :closed
|
||||
end
|
||||
end
|
||||
|
@ -50,29 +53,33 @@ module Callbacks
|
|||
puts text if @log
|
||||
end
|
||||
|
||||
def aasm_write_state(*args); log('aasm_write_state'); true; end
|
||||
def aasm_write_state(*args); log('aasm_write_state'); true; end
|
||||
def before_enter_open; log('before_enter_open'); end
|
||||
def enter_open; log('enter_open'); end
|
||||
def before_exit_open; log('before_exit_open'); end
|
||||
def after_enter_open; log('after_enter_open'); end
|
||||
def exit_open; log('exit_open'); end
|
||||
def after_exit_open; log('after_exit_open'); end
|
||||
|
||||
def before_enter_open; log('before_enter_open'); end
|
||||
def enter_open; log('enter_open'); end
|
||||
def before_exit_open; log('before_exit_open'); end
|
||||
def after_enter_open; log('after_enter_open'); end
|
||||
def exit_open; log('exit_open'); end
|
||||
def after_exit_open; log('after_exit_open'); end
|
||||
def before_enter_closed; log('before_enter_closed'); end
|
||||
def enter_closed; log('enter_closed'); end
|
||||
def before_exit_closed; log('before_exit_closed'); end
|
||||
def exit_closed; log('exit_closed'); end
|
||||
def after_enter_closed; log('after_enter_closed'); end
|
||||
def after_exit_closed; log('after_exit_closed'); end
|
||||
|
||||
def before_enter_closed; log('before_enter_closed'); end
|
||||
def enter_closed; log('enter_closed'); end
|
||||
def before_exit_closed; log('before_exit_closed'); end
|
||||
def exit_closed; log('exit_closed'); end
|
||||
def after_enter_closed; log('after_enter_closed'); end
|
||||
def after_exit_closed; log('after_exit_closed'); end
|
||||
def event_guard; log('event_guard'); !@fail_event_guard; end
|
||||
def transition_guard; log('transition_guard'); !@fail_transition_guard; end
|
||||
|
||||
def event_guard; log('event_guard'); !@fail_event_guard; end
|
||||
def transition_guard; log('transition_guard'); !@fail_transition_guard; end
|
||||
def after_transition; log('after_transition'); end
|
||||
def after_all_transitions; log('after_all_transitions'); end
|
||||
|
||||
def after_transition; log('after_transition'); end
|
||||
def after_all_transitions;log('after_all_transitions');end
|
||||
def before_all_events; log('before_all_events') end
|
||||
def before_event; log('before_event'); end
|
||||
def after_event; log('after_event'); end
|
||||
def after_all_events; log('after_all_events'); end
|
||||
|
||||
def before_event; log('before_event'); end
|
||||
def after_event; log('after_event'); end
|
||||
def ensure_event; log('ensure'); end
|
||||
def ensure_on_all_events; log('ensure'); end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,6 +1,65 @@
|
|||
require 'spec_helper'
|
||||
Dir[File.dirname(__FILE__) + "/../models/callbacks/*.rb"].sort.each { |f| require File.expand_path(f) }
|
||||
|
||||
shared_examples 'an implemented callback that accepts error' do
|
||||
context 'with callback defined' do
|
||||
it "should run error_callback if an exception is raised" do
|
||||
aasm_model.class.send(:define_method, callback_name) do |e|
|
||||
@data = [e]
|
||||
end
|
||||
|
||||
allow(aasm_model).to receive(:before_enter).and_raise(e = StandardError.new)
|
||||
|
||||
aasm_model.safe_close!
|
||||
expect(aasm_model.data).to eql [e]
|
||||
end
|
||||
|
||||
it "should run error_callback without parameters if callback does not support any" do
|
||||
aasm_model.class.send(:define_method, callback_name) do |e|
|
||||
@data = []
|
||||
end
|
||||
|
||||
allow(aasm_model).to receive(:before_enter).and_raise(e = StandardError.new)
|
||||
|
||||
aasm_model.safe_close!('arg1', 'arg2')
|
||||
expect(aasm_model.data).to eql []
|
||||
end
|
||||
|
||||
it "should run error_callback with parameters if callback supports them" do
|
||||
aasm_model.class.send(:define_method, callback_name) do |e, arg1, arg2|
|
||||
@data = [arg1, arg2]
|
||||
end
|
||||
|
||||
allow(aasm_model).to receive(:before_enter).and_raise(e = StandardError.new)
|
||||
|
||||
aasm_model.safe_close!('arg1', 'arg2')
|
||||
expect(aasm_model.data).to eql ['arg1', 'arg2']
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
shared_examples 'an implemented callback' do
|
||||
context 'with callback defined' do
|
||||
it 'should run callback without parameters if callback does not support any' do
|
||||
aasm_model.class.send(:define_method, callback_name) do
|
||||
@data = ['callback-was-called']
|
||||
end
|
||||
|
||||
aasm_model.safe_close!
|
||||
expect(aasm_model.data).to eql ['callback-was-called']
|
||||
end
|
||||
|
||||
it 'should run callback with parameters if callback supports them' do
|
||||
aasm_model.class.send(:define_method, callback_name) do |arg1, arg2|
|
||||
@data = [arg1, arg2]
|
||||
end
|
||||
|
||||
aasm_model.safe_close!('arg1', 'arg2')
|
||||
expect(aasm_model.data).to eql ['arg1', 'arg2']
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'callbacks for the new DSL' do
|
||||
|
||||
it "be called in order" do
|
||||
|
@ -10,6 +69,7 @@ describe 'callbacks for the new DSL' do
|
|||
callback.aasm.current_state
|
||||
|
||||
unless show_debug_log
|
||||
expect(callback).to receive(:before_all_events).once.ordered
|
||||
expect(callback).to receive(:before_event).once.ordered
|
||||
expect(callback).to receive(:event_guard).once.ordered.and_return(true)
|
||||
expect(callback).to receive(:transition_guard).once.ordered.and_return(true)
|
||||
|
@ -25,6 +85,9 @@ describe 'callbacks for the new DSL' do
|
|||
expect(callback).to receive(:after_exit_open).once.ordered # these should be after the state changes
|
||||
expect(callback).to receive(:after_enter_closed).once.ordered
|
||||
expect(callback).to receive(:after_event).once.ordered
|
||||
expect(callback).to receive(:after_all_events).once.ordered
|
||||
expect(callback).to receive(:ensure_event).once.ordered
|
||||
expect(callback).to receive(:ensure_on_all_events).once.ordered
|
||||
end
|
||||
|
||||
# puts "------- close!"
|
||||
|
@ -35,11 +98,13 @@ describe 'callbacks for the new DSL' do
|
|||
callback = Callbacks::Basic.new(:log => false)
|
||||
callback.aasm.current_state
|
||||
|
||||
expect(callback).to receive(:before_all_events).once.ordered
|
||||
expect(callback).to receive(:before_event).once.ordered
|
||||
expect(callback).to receive(:event_guard).once.ordered.and_return(false)
|
||||
expect(callback).to_not receive(:transition_guard)
|
||||
expect(callback).to_not receive(:before_exit_open)
|
||||
expect(callback).to_not receive(:exit_open)
|
||||
expect(callback).to_not receive(:after_all_transitions)
|
||||
expect(callback).to_not receive(:after_transition)
|
||||
expect(callback).to_not receive(:before_enter_closed)
|
||||
expect(callback).to_not receive(:enter_closed)
|
||||
|
@ -47,6 +112,9 @@ describe 'callbacks for the new DSL' do
|
|||
expect(callback).to_not receive(:after_exit_open)
|
||||
expect(callback).to_not receive(:after_enter_closed)
|
||||
expect(callback).to_not receive(:after_event)
|
||||
expect(callback).to_not receive(:after_all_events)
|
||||
expect(callback).to receive(:ensure_event).once.ordered
|
||||
expect(callback).to receive(:ensure_on_all_events).once.ordered
|
||||
|
||||
expect {
|
||||
callback.close!
|
||||
|
@ -72,11 +140,13 @@ describe 'callbacks for the new DSL' do
|
|||
callback.aasm.current_state
|
||||
|
||||
unless show_debug_log
|
||||
expect(callback).to receive(:before_all_events).once.ordered
|
||||
expect(callback).to receive(:before_event).once.ordered
|
||||
expect(callback).to receive(:event_guard).once.ordered.and_return(true)
|
||||
expect(callback).to receive(:transition_guard).once.ordered.and_return(false)
|
||||
expect(callback).to_not receive(:before_exit_open)
|
||||
expect(callback).to_not receive(:exit_open)
|
||||
expect(callback).to_not receive(:after_all_transitions)
|
||||
expect(callback).to_not receive(:after_transition)
|
||||
expect(callback).to_not receive(:before_enter_closed)
|
||||
expect(callback).to_not receive(:enter_closed)
|
||||
|
@ -84,6 +154,9 @@ describe 'callbacks for the new DSL' do
|
|||
expect(callback).to_not receive(:after_exit_open)
|
||||
expect(callback).to_not receive(:after_enter_closed)
|
||||
expect(callback).to_not receive(:after_event)
|
||||
expect(callback).to_not receive(:after_all_events)
|
||||
expect(callback).to receive(:ensure_event).once.ordered
|
||||
expect(callback).to receive(:ensure_on_all_events).once.ordered
|
||||
end
|
||||
|
||||
expect {
|
||||
|
@ -201,39 +274,9 @@ describe 'event callbacks' do
|
|||
@foo = Foo.new
|
||||
end
|
||||
|
||||
context "error_callback defined" do
|
||||
it "should run error_callback if an exception is raised" do
|
||||
def @foo.error_callback(e)
|
||||
@data = [e]
|
||||
end
|
||||
|
||||
allow(@foo).to receive(:before_enter).and_raise(e = StandardError.new)
|
||||
|
||||
@foo.safe_close!
|
||||
expect(@foo.data).to eql [e]
|
||||
end
|
||||
|
||||
it "should run error_callback without parameters if callback does not support any" do
|
||||
def @foo.error_callback(e)
|
||||
@data = []
|
||||
end
|
||||
|
||||
allow(@foo).to receive(:before_enter).and_raise(e = StandardError.new)
|
||||
|
||||
@foo.safe_close!('arg1', 'arg2')
|
||||
expect(@foo.data).to eql []
|
||||
end
|
||||
|
||||
it "should run error_callback with parameters if callback supports them" do
|
||||
def @foo.error_callback(e, arg1, arg2)
|
||||
@data = [arg1, arg2]
|
||||
end
|
||||
|
||||
allow(@foo).to receive(:before_enter).and_raise(e = StandardError.new)
|
||||
|
||||
@foo.safe_close!('arg1', 'arg2')
|
||||
expect(@foo.data).to eql ['arg1', 'arg2']
|
||||
end
|
||||
it_behaves_like 'an implemented callback that accepts error' do
|
||||
let(:aasm_model) { @foo }
|
||||
let(:callback_name) { :error_callback }
|
||||
end
|
||||
|
||||
it "should raise NoMethodError if exception is raised and error_callback is declared but not defined" do
|
||||
|
@ -242,8 +285,39 @@ describe 'event callbacks' do
|
|||
end
|
||||
|
||||
it "should propagate an error if no error callback is declared" do
|
||||
allow(@foo).to receive(:before_enter).and_raise("Cannot enter safe")
|
||||
expect{@foo.close!}.to raise_error(StandardError, "Cannot enter safe")
|
||||
allow(@foo).to receive(:before_enter).and_raise("Cannot enter safe")
|
||||
expect{@foo.close!}.to raise_error(StandardError, "Cannot enter safe")
|
||||
end
|
||||
end
|
||||
|
||||
describe 'with an ensure callback defined' do
|
||||
before do
|
||||
class Foo
|
||||
# this hack is needed to allow testing of parameters, since RSpec
|
||||
# destroys a method's arity when mocked
|
||||
attr_accessor :data
|
||||
|
||||
aasm do
|
||||
event :safe_close, :success => :success_callback, :ensure => :ensure_callback do
|
||||
transitions :to => :closed, :from => [:open]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@foo = Foo.new
|
||||
end
|
||||
|
||||
it_behaves_like 'an implemented callback' do
|
||||
let(:aasm_model) { @foo }
|
||||
let(:callback_name) { :ensure_callback }
|
||||
end
|
||||
|
||||
it "should raise NoMethodError if ensure_callback is declared but not defined" do
|
||||
expect{@foo.safe_close!}.to raise_error(NoMethodError)
|
||||
end
|
||||
|
||||
it "should not raise any error if no ensure_callback is declared" do
|
||||
expect{@foo.close!}.to_not raise_error
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -292,5 +366,76 @@ describe 'event callbacks' do
|
|||
@foo.close!
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
describe 'global error_on_all_events_callback callbacks' do
|
||||
describe "with an error_on_all_events" do
|
||||
before do
|
||||
class Foo
|
||||
# this hack is needed to allow testing of parameters, since RSpec
|
||||
# destroys a method's arity when mocked
|
||||
attr_accessor :data
|
||||
|
||||
aasm do
|
||||
error_on_all_events :error_on_all_events_callback
|
||||
|
||||
event :safe_close do
|
||||
transitions :to => :closed, :from => [:open]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@foo = Foo.new
|
||||
end
|
||||
|
||||
it_behaves_like 'an implemented callback that accepts error' do
|
||||
let(:aasm_model) { @foo }
|
||||
let(:callback_name) { :error_on_all_events_callback }
|
||||
end
|
||||
|
||||
it "should raise NoMethodError if exception is raised and error_callback is declared but not defined" do
|
||||
allow(@foo).to receive(:before_enter).and_raise(StandardError)
|
||||
expect{@foo.safe_close!}.to raise_error(NoMethodError)
|
||||
end
|
||||
|
||||
it "should raise NoMethodError if no error callback is declared" do
|
||||
allow(@foo).to receive(:before_enter).and_raise("Cannot enter safe")
|
||||
expect{@foo.close!}.to raise_error(NoMethodError)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'global ensure_on_all_events_callback callbacks' do
|
||||
describe "with an ensure_on_all_events" do
|
||||
before do
|
||||
class Foo
|
||||
# this hack is needed to allow testing of parameters, since RSpec
|
||||
# destroys a method's arity when mocked
|
||||
attr_accessor :data
|
||||
|
||||
aasm do
|
||||
ensure_on_all_events :ensure_on_all_events_callback
|
||||
|
||||
event :safe_close do
|
||||
transitions :to => :closed, :from => [:open]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@foo = Foo.new
|
||||
end
|
||||
|
||||
it_behaves_like 'an implemented callback' do
|
||||
let(:aasm_model) { @foo }
|
||||
let(:callback_name) { :ensure_on_all_events_callback }
|
||||
end
|
||||
|
||||
it "should raise NoMethodError if ensure_on_all_events callback is declared but not defined" do
|
||||
expect{@foo.safe_close!}.to raise_error(NoMethodError)
|
||||
end
|
||||
|
||||
it "should raise NoMethodError if no ensure_on_all_events callback is declared" do
|
||||
expect{@foo.close!}.to raise_error(NoMethodError)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Add table
Reference in a new issue