mirror of
https://github.com/aasm/aasm
synced 2023-03-27 23:22:41 -04:00
Deep clone state machine during inheritance (with tests) (#429)
* Deep clone state machine during inheritance. Allows child classes to extend the parent state machine without modifying the parents state machine. * Add tests * Also need to clone global callbacks to support inheritance * Added tests to ensure parent is not changed in inheritance
This commit is contained in:
parent
e1adfcd126
commit
77cd065eeb
7 changed files with 118 additions and 6 deletions
|
@ -27,6 +27,16 @@ module AASM::Core
|
|||
], &block) if block
|
||||
end
|
||||
|
||||
# called internally by Ruby 1.9 after clone()
|
||||
def initialize_copy(orig)
|
||||
super
|
||||
@transitions = @transitions.collect { |transition| transition.clone }
|
||||
@guards = @guards.dup
|
||||
@unless = @unless.dup
|
||||
@options = {}
|
||||
orig.options.each_pair { |name, setting| @options[name] = setting.is_a?(Hash) || setting.is_a?(Array) ? setting.dup : setting }
|
||||
end
|
||||
|
||||
# a neutered version of fire - it doesn't actually fire the event, it just
|
||||
# executes the transition guards to determine if a transition is even
|
||||
# an option given current conditions.
|
||||
|
|
|
@ -9,6 +9,13 @@ module AASM::Core
|
|||
update(options)
|
||||
end
|
||||
|
||||
# called internally by Ruby 1.9 after clone()
|
||||
def initialize_copy(orig)
|
||||
super
|
||||
@options = {}
|
||||
orig.options.each_pair { |name, setting| @options[name] = setting.is_a?(Hash) || setting.is_a?(Array) ? setting.dup : setting }
|
||||
end
|
||||
|
||||
def ==(state)
|
||||
if state.is_a? Symbol
|
||||
name == state
|
||||
|
|
|
@ -28,6 +28,15 @@ module AASM::Core
|
|||
@opts = opts
|
||||
end
|
||||
|
||||
# called internally by Ruby 1.9 after clone()
|
||||
def initialize_copy(orig)
|
||||
super
|
||||
@guards = @guards.dup
|
||||
@unless = @unless.dup
|
||||
@opts = {}
|
||||
orig.opts.each_pair { |name, setting| @opts[name] = setting.is_a?(Hash) || setting.is_a?(Array) ? setting.dup : setting }
|
||||
end
|
||||
|
||||
def allowed?(obj, *args)
|
||||
invoke_callbacks_compatible_with_guard(@guards, obj, args, :guard => true) &&
|
||||
invoke_callbacks_compatible_with_guard(@unless, obj, args, :unless => true)
|
||||
|
|
|
@ -16,8 +16,10 @@ module AASM
|
|||
# called internally by Ruby 1.9 after clone()
|
||||
def initialize_copy(orig)
|
||||
super
|
||||
@states = @states.dup
|
||||
@events = @events.dup
|
||||
@states = orig.states.collect { |state| state.clone }
|
||||
@events = {}
|
||||
orig.events.each_pair { |name, event| @events[name] = event.clone }
|
||||
@global_callbacks = @global_callbacks.dup
|
||||
end
|
||||
|
||||
def add_state(state_name, klass, options)
|
||||
|
|
|
@ -1,7 +1,41 @@
|
|||
require_relative 'super_class'
|
||||
|
||||
class SubClass < SuperClass
|
||||
# Add an after callback that is not defined in the parent
|
||||
aasm.state_machine.events[:foo].options[:after] = [:after_foo_event]
|
||||
|
||||
# Add global callback that is not defined in the parent
|
||||
aasm.state_machine.global_callbacks[:after_all_transitions] = :after_all_event
|
||||
|
||||
attr_accessor :called_after
|
||||
|
||||
def after_foo_event
|
||||
self.called_after = true
|
||||
end
|
||||
|
||||
def after_all_event; end
|
||||
end
|
||||
|
||||
class SubClassMultiple < SuperClassMultiple
|
||||
# Add after callbacks that are not defined in the parent
|
||||
aasm(:left).state_machine.events[:foo].options[:after] = [:left_after_foo_event]
|
||||
aasm(:right).state_machine.events[:close].options[:after] = [:right_after_close_event]
|
||||
|
||||
# Add global callback that is not defined in the parent
|
||||
aasm(:left).state_machine.global_callbacks[:after_all_transitions] = :left_after_all_event
|
||||
aasm(:right).state_machine.global_callbacks[:after_all_transitions] = :right_after_all_event
|
||||
|
||||
attr_accessor :left_called_after, :right_called_after
|
||||
|
||||
def left_after_foo_event
|
||||
self.left_called_after = true
|
||||
end
|
||||
|
||||
def right_after_close_event
|
||||
self.right_called_after = true
|
||||
end
|
||||
|
||||
def left_after_all_event; end
|
||||
|
||||
def right_after_all_event; end
|
||||
end
|
||||
|
|
|
@ -19,7 +19,7 @@ describe 'subclassing with multiple state machines' do
|
|||
expect(SuperClassMultiple.aasm(:right).states).not_to include(:archived)
|
||||
end
|
||||
|
||||
it "should have the same events as its parent" do
|
||||
it 'should have the same events as its parent' do
|
||||
expect(SubClassMultiple.aasm(:left).events).to eq(SuperClassMultiple.aasm(:left).events)
|
||||
expect(SubClassMultiple.aasm(:right).events).to eq(SuperClassMultiple.aasm(:right).events)
|
||||
end
|
||||
|
@ -35,5 +35,40 @@ describe 'subclassing with multiple state machines' do
|
|||
expect(son.aasm(:left).current_state).to eq(:ended)
|
||||
end
|
||||
|
||||
end
|
||||
it 'should allow the child to modify its left state machine' do
|
||||
son = SubClassMultiple.new
|
||||
expect(son.left_called_after).to eq(nil)
|
||||
expect(son.right_called_after).to eq(nil)
|
||||
son.foo
|
||||
expect(son.left_called_after).to eq(true)
|
||||
expect(son.right_called_after).to eq(nil)
|
||||
global_callbacks = SubClassMultiple.aasm(:left).state_machine.global_callbacks
|
||||
expect(global_callbacks).to_not be_empty
|
||||
expect(global_callbacks[:after_all_transitions]).to eq :left_after_all_event
|
||||
end
|
||||
|
||||
it 'should allow the child to modify its right state machine' do
|
||||
son = SubClassMultiple.new
|
||||
expect(son.right_called_after).to eq(nil)
|
||||
expect(son.left_called_after).to eq(nil)
|
||||
son.close
|
||||
expect(son.right_called_after).to eq(true)
|
||||
expect(son.left_called_after).to eq(nil)
|
||||
global_callbacks = SubClassMultiple.aasm(:right).state_machine.global_callbacks
|
||||
expect(global_callbacks).to_not be_empty
|
||||
expect(global_callbacks[:after_all_transitions]).to eq :right_after_all_event
|
||||
end
|
||||
|
||||
it 'should not modify the parent left state machine' do
|
||||
super_class_event = SuperClassMultiple.aasm(:left).events.select { |event| event.name == :foo }.first
|
||||
expect(super_class_event.options).to be_empty
|
||||
expect(SuperClassMultiple.aasm(:left).state_machine.global_callbacks).to be_empty
|
||||
end
|
||||
|
||||
it 'should not modify the parent right state machine' do
|
||||
super_class_event = SuperClassMultiple.aasm(:right).events.select { |event| event.name == :close }.first
|
||||
expect(super_class_event.options).to be_empty
|
||||
expect(SuperClassMultiple.aasm(:right).state_machine.global_callbacks).to be_empty
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -13,7 +13,7 @@ describe 'subclassing' do
|
|||
expect(SuperClass.aasm.states).not_to include(:foo)
|
||||
end
|
||||
|
||||
it "should have the same events as its parent" do
|
||||
it 'should have the same events as its parent' do
|
||||
expect(SubClass.aasm.events).to eq(SuperClass.aasm.events)
|
||||
end
|
||||
|
||||
|
@ -27,5 +27,20 @@ describe 'subclassing' do
|
|||
expect(son.aasm.current_state).to eq(:ended)
|
||||
end
|
||||
|
||||
end
|
||||
it 'should allow the child to modify its state machine' do
|
||||
son = SubClass.new
|
||||
expect(son.called_after).to eq(nil)
|
||||
son.foo
|
||||
expect(son.called_after).to eq(true)
|
||||
global_callbacks = SubClass.aasm.state_machine.global_callbacks
|
||||
expect(global_callbacks).to_not be_empty
|
||||
expect(global_callbacks[:after_all_transitions]).to eq :after_all_event
|
||||
end
|
||||
|
||||
it 'should not modify the parent state machine' do
|
||||
super_class_event = SuperClass.aasm.events.select { |event| event.name == :foo }.first
|
||||
expect(super_class_event.options).to be_empty
|
||||
expect(SuperClass.aasm.state_machine.global_callbacks).to be_empty
|
||||
end
|
||||
|
||||
end
|
||||
|
|
Loading…
Reference in a new issue