1
0
Fork 0
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:
HoyaBoya 2015-11-04 15:54:58 -05:00
parent a31b02cc2b
commit e64a84f71f
6 changed files with 254 additions and 61 deletions

View file

@ -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
```

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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