aasm 3.0.14 → 3.0.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/CHANGELOG.md +4 -0
- data/lib/aasm/aasm.rb +9 -8
- data/lib/aasm/base.rb +6 -1
- data/lib/aasm/state_machine.rb +8 -3
- data/lib/aasm/supporting_classes/localizer.rb +6 -7
- data/lib/aasm/supporting_classes/state.rb +17 -2
- data/lib/aasm/version.rb +1 -1
- data/spec/en.yml +1 -1
- data/spec/en_deprecated_style.yml +1 -1
- data/spec/models/argument.rb +11 -0
- data/spec/models/callback_new_dsl.rb +5 -0
- data/spec/models/callback_old_dsl.rb +11 -6
- data/spec/models/parametrised_event.rb +29 -0
- data/spec/spec_helpers/models_spec_helper.rb +0 -55
- data/spec/unit/callbacks_spec.rb +116 -0
- data/spec/unit/complex_example_spec.rb +2 -2
- data/spec/unit/initial_state_spec.rb +28 -0
- data/spec/unit/inspection_spec.rb +75 -3
- data/spec/unit/memory_leak_spec.rb +32 -28
- data/spec/unit/new_dsl_spec.rb +2 -18
- data/spec/unit/persistence/active_record_persistence_spec.rb +2 -7
- data/spec/unit/simple_example_spec.rb +53 -0
- data/spec/unit/subclassing_spec.rb +19 -0
- data/spec/unit/{event_spec.rb → supporting_classes/event_spec.rb} +77 -24
- data/spec/unit/{localizer_spec.rb → supporting_classes/localizer_spec.rb} +25 -3
- data/spec/unit/{state_spec.rb → supporting_classes/state_spec.rb} +2 -2
- data/spec/unit/{state_transition_spec.rb → supporting_classes/state_transition_spec.rb} +2 -2
- metadata +40 -34
- data/spec/unit/aasm_spec.rb +0 -301
- data/spec/unit/callbacks_new_dsl_spec.rb +0 -33
- data/spec/unit/callbacks_old_dsl_spec.rb +0 -33
data/CHANGELOG.md
CHANGED
data/lib/aasm/aasm.rb
CHANGED
@@ -2,15 +2,14 @@ module AASM
|
|
2
2
|
|
3
3
|
def self.included(base) #:nodoc:
|
4
4
|
base.extend AASM::ClassMethods
|
5
|
-
|
6
|
-
unless AASM::StateMachine[base]
|
7
|
-
AASM::StateMachine[base] = AASM::StateMachine.new('')
|
8
|
-
end
|
5
|
+
AASM::StateMachine[base] ||= AASM::StateMachine.new('')
|
9
6
|
AASM::Persistence.set_persistence(base)
|
10
7
|
super
|
11
8
|
end
|
12
9
|
|
13
10
|
module ClassMethods
|
11
|
+
|
12
|
+
# make sure inheritance (aka subclassing) works with AASM
|
14
13
|
def inherited(klass)
|
15
14
|
AASM::StateMachine[klass] = AASM::StateMachine[self].clone
|
16
15
|
super
|
@@ -18,13 +17,14 @@ module AASM
|
|
18
17
|
|
19
18
|
def aasm(options={}, &block)
|
20
19
|
@aasm ||= AASM::Base.new(self, options)
|
21
|
-
@aasm.instance_eval(&block) if block
|
20
|
+
@aasm.instance_eval(&block) if block # new DSL
|
22
21
|
@aasm
|
23
22
|
end
|
24
23
|
|
24
|
+
# TODO: maybe better: aasm.initial_state
|
25
25
|
def aasm_initial_state(set_state=nil)
|
26
26
|
if set_state
|
27
|
-
# deprecated
|
27
|
+
# deprecated way to set the value
|
28
28
|
AASM::StateMachine[self].initial_state = set_state
|
29
29
|
else
|
30
30
|
AASM::StateMachine[self].initial_state
|
@@ -69,7 +69,8 @@ module AASM
|
|
69
69
|
aasm.states_for_select
|
70
70
|
end
|
71
71
|
|
72
|
-
|
72
|
+
# aasm.event(:event_name).human?
|
73
|
+
def aasm_human_event_name(event) # event_name?
|
73
74
|
AASM::SupportingClasses::Localizer.new.human_event_name(self, event)
|
74
75
|
end
|
75
76
|
end # ClassMethods
|
@@ -110,7 +111,7 @@ module AASM
|
|
110
111
|
end
|
111
112
|
|
112
113
|
def aasm_human_state
|
113
|
-
AASM::SupportingClasses::Localizer.new.
|
114
|
+
AASM::SupportingClasses::Localizer.new.human_state_name(self.class, aasm_current_state)
|
114
115
|
end
|
115
116
|
|
116
117
|
private
|
data/lib/aasm/base.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
module AASM
|
2
2
|
class Base
|
3
|
+
|
3
4
|
def initialize(clazz, options={}, &block)
|
4
5
|
@clazz = clazz
|
5
6
|
sm = AASM::StateMachine[@clazz]
|
@@ -18,10 +19,14 @@ module AASM
|
|
18
19
|
end
|
19
20
|
end
|
20
21
|
|
22
|
+
def initial_state
|
23
|
+
AASM::StateMachine[@clazz].initial_state
|
24
|
+
end
|
25
|
+
|
21
26
|
def state(name, options={})
|
22
27
|
# @clazz.aasm_state(name, options)
|
23
28
|
sm = AASM::StateMachine[@clazz]
|
24
|
-
sm.create_state(name, options)
|
29
|
+
sm.create_state(name, @clazz, options)
|
25
30
|
sm.initial_state = name if options[:initial] || !sm.initial_state
|
26
31
|
|
27
32
|
@clazz.send(:define_method, "#{name.to_s}?") do
|
data/lib/aasm/state_machine.rb
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
module AASM
|
2
2
|
class StateMachine
|
3
|
+
|
4
|
+
# the following two methods provide the storage of all state machines
|
3
5
|
def self.[](clazz)
|
4
6
|
(@machines ||= {})[clazz.to_s]
|
5
7
|
end
|
@@ -11,6 +13,7 @@ module AASM
|
|
11
13
|
attr_accessor :states, :events, :initial_state, :config
|
12
14
|
attr_reader :name
|
13
15
|
|
16
|
+
# QUESTION: what's the name for? [alto, 2012-11-28]
|
14
17
|
def initialize(name)
|
15
18
|
@name = name
|
16
19
|
@initial_state = nil
|
@@ -19,14 +22,16 @@ module AASM
|
|
19
22
|
@config = OpenStruct.new
|
20
23
|
end
|
21
24
|
|
25
|
+
# called internally by Ruby 1.9 after clone()
|
22
26
|
def initialize_copy(orig)
|
23
27
|
super
|
24
28
|
@states = @states.dup
|
25
29
|
@events = @events.dup
|
26
30
|
end
|
27
31
|
|
28
|
-
def create_state(name, options)
|
29
|
-
@states << AASM::SupportingClasses::State.new(name, options) unless @states.include?(name)
|
32
|
+
def create_state(name, clazz, options)
|
33
|
+
@states << AASM::SupportingClasses::State.new(name, clazz, options) unless @states.include?(name)
|
30
34
|
end
|
31
|
-
|
35
|
+
|
36
|
+
end # StateMachine
|
32
37
|
end # AASM
|
@@ -9,21 +9,20 @@ module AASM
|
|
9
9
|
translate_queue(checklist) || I18n.translate(checklist.shift, :default => event.to_s.humanize)
|
10
10
|
end
|
11
11
|
|
12
|
-
def
|
13
|
-
klass = obj.class
|
12
|
+
def human_state_name(klass, state)
|
14
13
|
checklist = ancestors_list(klass).inject([]) do |list, ancestor|
|
15
|
-
list << item_for(
|
16
|
-
list << item_for(
|
14
|
+
list << item_for(klass, state, ancestor)
|
15
|
+
list << item_for(klass, state, ancestor, :old_style => true)
|
17
16
|
list
|
18
17
|
end
|
19
|
-
translate_queue(checklist) || I18n.translate(checklist.shift, :default =>
|
18
|
+
translate_queue(checklist) || I18n.translate(checklist.shift, :default => state.to_s.humanize)
|
20
19
|
end
|
21
20
|
|
22
21
|
private
|
23
22
|
|
24
|
-
def item_for(
|
23
|
+
def item_for(klass, state, ancestor, options={})
|
25
24
|
separator = options[:old_style] ? '.' : '/'
|
26
|
-
:"#{i18n_scope(klass)}.attributes.#{i18n_klass(ancestor)}.#{klass.aasm_column}#{separator}#{
|
25
|
+
:"#{i18n_scope(klass)}.attributes.#{i18n_klass(ancestor)}.#{klass.aasm_column}#{separator}#{state}"
|
27
26
|
end
|
28
27
|
|
29
28
|
def translate_queue(checklist)
|
@@ -3,8 +3,9 @@ module AASM
|
|
3
3
|
class State
|
4
4
|
attr_reader :name, :options
|
5
5
|
|
6
|
-
def initialize(name, options={})
|
6
|
+
def initialize(name, clazz, options={})
|
7
7
|
@name = name
|
8
|
+
@clazz = clazz
|
8
9
|
update(options)
|
9
10
|
end
|
10
11
|
|
@@ -24,6 +25,10 @@ module AASM
|
|
24
25
|
end
|
25
26
|
end
|
26
27
|
|
28
|
+
def to_s
|
29
|
+
name.to_s
|
30
|
+
end
|
31
|
+
|
27
32
|
def fire_callbacks(action, record)
|
28
33
|
action = @options[action]
|
29
34
|
catch :halt_aasm_chain do
|
@@ -34,7 +39,17 @@ module AASM
|
|
34
39
|
end
|
35
40
|
|
36
41
|
def display_name
|
37
|
-
@display_name ||=
|
42
|
+
@display_name ||= begin
|
43
|
+
if Module.const_defined?(:I18n)
|
44
|
+
localized_name
|
45
|
+
else
|
46
|
+
name.to_s.gsub(/_/, ' ').capitalize
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def localized_name
|
52
|
+
AASM::SupportingClasses::Localizer.new.human_state_name(@clazz, self)
|
38
53
|
end
|
39
54
|
|
40
55
|
def for_select
|
data/lib/aasm/version.rb
CHANGED
data/spec/en.yml
CHANGED
@@ -6,10 +6,12 @@ class CallbackNewDsl
|
|
6
6
|
:before_enter => :before_enter_open,
|
7
7
|
:after_enter => :after_enter_open,
|
8
8
|
:before_exit => :before_exit_open,
|
9
|
+
:exit => :exit_open,
|
9
10
|
:after_exit => :after_exit_open
|
10
11
|
|
11
12
|
state :closed,
|
12
13
|
:before_enter => :before_enter_closed,
|
14
|
+
:enter => :enter_closed,
|
13
15
|
:after_enter => :after_enter_closed,
|
14
16
|
:before_exit => :before_exit_closed,
|
15
17
|
:after_exit => :after_exit_closed
|
@@ -35,4 +37,7 @@ class CallbackNewDsl
|
|
35
37
|
|
36
38
|
def before; end
|
37
39
|
def after; end
|
40
|
+
|
41
|
+
def enter_closed; end
|
42
|
+
def exit_open; end
|
38
43
|
end
|
@@ -4,14 +4,16 @@ class CallbackOldDsl
|
|
4
4
|
aasm_initial_state :open
|
5
5
|
aasm_state :open,
|
6
6
|
:before_enter => :before_enter_open,
|
7
|
-
:
|
8
|
-
:
|
9
|
-
:
|
7
|
+
:after_enter => :after_enter_open,
|
8
|
+
:before_exit => :before_exit_open,
|
9
|
+
:exit => :exit_open,
|
10
|
+
:after_exit => :after_exit_open
|
10
11
|
aasm_state :closed,
|
11
12
|
:before_enter => :before_enter_closed,
|
12
|
-
:
|
13
|
-
:after_enter
|
14
|
-
:
|
13
|
+
:enter => :enter_closed,
|
14
|
+
:after_enter => :after_enter_closed,
|
15
|
+
:before_exit => :before_exit_closed,
|
16
|
+
:after_exit => :after_exit_closed
|
15
17
|
|
16
18
|
aasm_event :close, :before => :before, :after => :after do
|
17
19
|
transitions :to => :closed, :from => [:open]
|
@@ -33,4 +35,7 @@ class CallbackOldDsl
|
|
33
35
|
|
34
36
|
def before; end
|
35
37
|
def after; end
|
38
|
+
|
39
|
+
def enter_closed; end
|
40
|
+
def exit_open; end
|
36
41
|
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
class ParametrisedEvent
|
2
|
+
include AASM
|
3
|
+
aasm do
|
4
|
+
state :sleeping, :initial => true
|
5
|
+
state :showering
|
6
|
+
state :working
|
7
|
+
state :dating
|
8
|
+
state :prettying_up
|
9
|
+
|
10
|
+
event :wakeup do
|
11
|
+
transitions :from => :sleeping, :to => [:showering, :working]
|
12
|
+
end
|
13
|
+
|
14
|
+
event :dress do
|
15
|
+
transitions :from => :sleeping, :to => :working, :on_transition => :wear_clothes
|
16
|
+
transitions :from => :showering, :to => [:working, :dating], :on_transition => Proc.new { |obj, *args| obj.wear_clothes(*args) }
|
17
|
+
transitions :from => :showering, :to => :prettying_up, :on_transition => [:condition_hair, :fix_hair]
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def wear_clothes(shirt_color, trouser_type)
|
22
|
+
end
|
23
|
+
|
24
|
+
def condition_hair
|
25
|
+
end
|
26
|
+
|
27
|
+
def fix_hair
|
28
|
+
end
|
29
|
+
end
|
@@ -51,31 +51,6 @@ end
|
|
51
51
|
class Baz < Bar
|
52
52
|
end
|
53
53
|
|
54
|
-
class Banker
|
55
|
-
include AASM
|
56
|
-
aasm do
|
57
|
-
state :retired
|
58
|
-
state :selling_bad_mortgages
|
59
|
-
end
|
60
|
-
aasm_initial_state Proc.new { |banker| banker.rich? ? :retired : :selling_bad_mortgages }
|
61
|
-
RICH = 1_000_000
|
62
|
-
attr_accessor :balance
|
63
|
-
def initialize(balance = 0); self.balance = balance; end
|
64
|
-
def rich?; self.balance >= RICH; end
|
65
|
-
end
|
66
|
-
|
67
|
-
class Argument
|
68
|
-
include AASM
|
69
|
-
aasm do
|
70
|
-
state :invalid, :initial => true
|
71
|
-
state :valid
|
72
|
-
|
73
|
-
event :valid do
|
74
|
-
transitions :to => :valid, :from => [:invalid]
|
75
|
-
end
|
76
|
-
end
|
77
|
-
end
|
78
|
-
|
79
54
|
class ThisNameBetterNotBeInUse
|
80
55
|
include AASM
|
81
56
|
|
@@ -87,33 +62,3 @@ class ThisNameBetterNotBeInUse
|
|
87
62
|
state :proc
|
88
63
|
end
|
89
64
|
end
|
90
|
-
|
91
|
-
class ChetanPatil
|
92
|
-
include AASM
|
93
|
-
aasm do
|
94
|
-
state :sleeping, :initial => true
|
95
|
-
state :showering
|
96
|
-
state :working
|
97
|
-
state :dating
|
98
|
-
state :prettying_up
|
99
|
-
|
100
|
-
event :wakeup do
|
101
|
-
transitions :from => :sleeping, :to => [:showering, :working]
|
102
|
-
end
|
103
|
-
|
104
|
-
event :dress do
|
105
|
-
transitions :from => :sleeping, :to => :working, :on_transition => :wear_clothes
|
106
|
-
transitions :from => :showering, :to => [:working, :dating], :on_transition => Proc.new { |obj, *args| obj.wear_clothes(*args) }
|
107
|
-
transitions :from => :showering, :to => :prettying_up, :on_transition => [:condition_hair, :fix_hair]
|
108
|
-
end
|
109
|
-
end
|
110
|
-
|
111
|
-
def wear_clothes(shirt_color, trouser_type)
|
112
|
-
end
|
113
|
-
|
114
|
-
def condition_hair
|
115
|
-
end
|
116
|
-
|
117
|
-
def fix_hair
|
118
|
-
end
|
119
|
-
end
|
@@ -0,0 +1,116 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe 'callbacks for the old DSL' do
|
4
|
+
let(:callback) {CallbackOldDsl.new}
|
5
|
+
|
6
|
+
it "should get close callbacks" do
|
7
|
+
callback.should_receive(:exit_open).once.ordered
|
8
|
+
callback.should_receive(:before).once.ordered
|
9
|
+
callback.should_receive(:before_exit_open).once.ordered # these should be before the state changes
|
10
|
+
callback.should_receive(:before_enter_closed).once.ordered
|
11
|
+
callback.should_receive(:enter_closed).once.ordered
|
12
|
+
callback.should_receive(:aasm_write_state).once.ordered.and_return(true) # this is when the state changes
|
13
|
+
callback.should_receive(:after_exit_open).once.ordered # these should be after the state changes
|
14
|
+
callback.should_receive(:after_enter_closed).once.ordered
|
15
|
+
callback.should_receive(:after).once.ordered
|
16
|
+
|
17
|
+
callback.close!
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
describe 'callbacks for the new DSL' do
|
22
|
+
let(:callback) {CallbackNewDsl.new}
|
23
|
+
|
24
|
+
it "be called in order" do
|
25
|
+
callback.should_receive(:exit_open).once.ordered
|
26
|
+
callback.should_receive(:before).once.ordered
|
27
|
+
callback.should_receive(:before_exit_open).once.ordered # these should be before the state changes
|
28
|
+
callback.should_receive(:before_enter_closed).once.ordered
|
29
|
+
callback.should_receive(:enter_closed).once.ordered
|
30
|
+
callback.should_receive(:aasm_write_state).once.ordered.and_return(true) # this is when the state changes
|
31
|
+
callback.should_receive(:after_exit_open).once.ordered # these should be after the state changes
|
32
|
+
callback.should_receive(:after_enter_closed).once.ordered
|
33
|
+
callback.should_receive(:after).once.ordered
|
34
|
+
|
35
|
+
callback.close!
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
describe 'event callbacks' do
|
40
|
+
describe "with an error callback defined" do
|
41
|
+
before do
|
42
|
+
class Foo
|
43
|
+
aasm_event :safe_close, :success => :success_callback, :error => :error_callback do
|
44
|
+
transitions :to => :closed, :from => [:open]
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
@foo = Foo.new
|
49
|
+
end
|
50
|
+
|
51
|
+
it "should run error_callback if an exception is raised and error_callback defined" do
|
52
|
+
def @foo.error_callback(e); end
|
53
|
+
|
54
|
+
@foo.stub!(:enter).and_raise(e=StandardError.new)
|
55
|
+
@foo.should_receive(:error_callback).with(e)
|
56
|
+
|
57
|
+
@foo.safe_close!
|
58
|
+
end
|
59
|
+
|
60
|
+
it "should raise NoMethodError if exceptionis raised and error_callback is declared but not defined" do
|
61
|
+
@foo.stub!(:enter).and_raise(StandardError)
|
62
|
+
lambda{@foo.safe_close!}.should raise_error(NoMethodError)
|
63
|
+
end
|
64
|
+
|
65
|
+
it "should propagate an error if no error callback is declared" do
|
66
|
+
@foo.stub!(:enter).and_raise("Cannot enter safe")
|
67
|
+
lambda{@foo.close!}.should raise_error(StandardError, "Cannot enter safe")
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
describe "with aasm_event_fired defined" do
|
72
|
+
before do
|
73
|
+
@foo = Foo.new
|
74
|
+
def @foo.aasm_event_fired(event, from, to); end
|
75
|
+
end
|
76
|
+
|
77
|
+
it 'should call it for successful bang fire' do
|
78
|
+
@foo.should_receive(:aasm_event_fired).with(:close, :open, :closed)
|
79
|
+
@foo.close!
|
80
|
+
end
|
81
|
+
|
82
|
+
it 'should call it for successful non-bang fire' do
|
83
|
+
@foo.should_receive(:aasm_event_fired)
|
84
|
+
@foo.close
|
85
|
+
end
|
86
|
+
|
87
|
+
it 'should not call it for failing bang fire' do
|
88
|
+
@foo.stub!(:aasm_set_current_state_with_persistence).and_return(false)
|
89
|
+
@foo.should_not_receive(:aasm_event_fired)
|
90
|
+
@foo.close!
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
describe "with aasm_event_failed defined" do
|
95
|
+
before do
|
96
|
+
@foo = Foo.new
|
97
|
+
def @foo.aasm_event_failed(event, from); end
|
98
|
+
end
|
99
|
+
|
100
|
+
it 'should call it when transition failed for bang fire' do
|
101
|
+
@foo.should_receive(:aasm_event_failed).with(:null, :open)
|
102
|
+
lambda {@foo.null!}.should raise_error(AASM::InvalidTransition)
|
103
|
+
end
|
104
|
+
|
105
|
+
it 'should call it when transition failed for non-bang fire' do
|
106
|
+
@foo.should_receive(:aasm_event_failed).with(:null, :open)
|
107
|
+
lambda {@foo.null}.should raise_error(AASM::InvalidTransition)
|
108
|
+
end
|
109
|
+
|
110
|
+
it 'should not call it if persist fails for bang fire' do
|
111
|
+
@foo.stub!(:aasm_set_current_state_with_persistence).and_return(false)
|
112
|
+
@foo.should_receive(:aasm_event_failed)
|
113
|
+
@foo.close!
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|