aasm 3.4.0 → 4.0.0
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.
- checksums.yaml +4 -4
- data/.gitignore +1 -1
- data/CHANGELOG.md +20 -3
- data/PLANNED_CHANGES.md +7 -0
- data/README.md +132 -39
- data/README_FROM_VERSION_3_TO_4.md +240 -0
- data/callbacks.txt +51 -0
- data/lib/aasm.rb +2 -0
- data/lib/aasm/aasm.rb +36 -99
- data/lib/aasm/base.rb +18 -7
- data/lib/aasm/configuration.rb +23 -0
- data/lib/aasm/dsl_helper.rb +30 -0
- data/lib/aasm/errors.rb +1 -0
- data/lib/aasm/event.rb +25 -26
- data/lib/aasm/instance_base.rb +15 -15
- data/lib/aasm/persistence/active_record_persistence.rb +2 -2
- data/lib/aasm/state_machine.rb +1 -1
- data/lib/aasm/transition.rb +44 -24
- data/lib/aasm/version.rb +1 -1
- data/spec/database.rb +1 -1
- data/spec/models/auth_machine.rb +10 -6
- data/spec/models/callback_new_dsl.rb +99 -13
- data/spec/models/double_definer.rb +2 -2
- data/spec/models/foo.rb +4 -4
- data/spec/models/parametrised_event.rb +3 -3
- data/spec/models/persistence.rb +16 -1
- data/spec/models/validator.rb +6 -3
- data/spec/unit/callbacks_spec.rb +106 -5
- data/spec/unit/event_spec.rb +4 -4
- data/spec/unit/inspection_spec.rb +7 -7
- data/spec/unit/localizer_spec.rb +1 -0
- data/spec/unit/persistence/active_record_persistence_spec.rb +20 -2
- data/spec/unit/transition_spec.rb +66 -18
- metadata +7 -2
data/lib/aasm/instance_base.rb
CHANGED
@@ -21,7 +21,7 @@ module AASM
|
|
21
21
|
state_object = state_object_for_name(state_name)
|
22
22
|
|
23
23
|
state_object.fire_callbacks(:before_enter, @instance)
|
24
|
-
state_object.fire_callbacks(:enter, @instance)
|
24
|
+
# state_object.fire_callbacks(:enter, @instance)
|
25
25
|
self.current_state = state_name
|
26
26
|
state_object.fire_callbacks(:after_enter, @instance)
|
27
27
|
|
@@ -33,9 +33,10 @@ module AASM
|
|
33
33
|
end
|
34
34
|
|
35
35
|
def states(options={})
|
36
|
-
if options[:
|
36
|
+
if options[:permitted]
|
37
37
|
# ugliness level 1000
|
38
|
-
|
38
|
+
permitted_event_names = events(:permitted => true).map(&:name)
|
39
|
+
transitions = @instance.class.aasm.state_machine.events.values_at(*permitted_event_names).compact.map {|e| e.transitions_from_state(current_state) }
|
39
40
|
tos = transitions.map {|t| t[0] ? t[0].to : nil}.flatten.compact.map(&:to_sym).uniq
|
40
41
|
@instance.class.aasm.states.select {|s| tos.include?(s.name.to_sym)}
|
41
42
|
else
|
@@ -43,18 +44,17 @@ module AASM
|
|
43
44
|
end
|
44
45
|
end
|
45
46
|
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
47
|
+
def events(options={})
|
48
|
+
state = options[:state] || current_state
|
49
|
+
events = @instance.class.aasm.events.select {|e| e.transitions_from_state?(state) }
|
50
|
+
|
51
|
+
if options[:permitted]
|
52
|
+
# filters the results of events_for_current_state so that only those that
|
53
|
+
# are really currently possible (given transition guards) are shown.
|
54
|
+
events.select! { |e| @instance.send("may_#{e.name}?") }
|
55
|
+
end
|
52
56
|
|
53
|
-
|
54
|
-
# are really currently possible (given transition guards) are shown.
|
55
|
-
# QUESTION: what about events.permissible ?
|
56
|
-
def permissible_events
|
57
|
-
events.select{ |e| @instance.send(("may_" + e.to_s + "?").to_sym) }
|
57
|
+
events
|
58
58
|
end
|
59
59
|
|
60
60
|
def state_object_for_name(name)
|
@@ -75,7 +75,7 @@ module AASM
|
|
75
75
|
end
|
76
76
|
|
77
77
|
def may_fire_event?(name, *args)
|
78
|
-
if event = @instance.class.aasm.events[name]
|
78
|
+
if event = @instance.class.aasm.state_machine.events[name]
|
79
79
|
event.may_fire?(@instance, *args)
|
80
80
|
else
|
81
81
|
false # unknown event
|
@@ -174,8 +174,8 @@ module AASM
|
|
174
174
|
success = options[:persist] ? self.class.transaction(:requires_new => requires_new?) { super } : super
|
175
175
|
|
176
176
|
if success && options[:persist]
|
177
|
-
|
178
|
-
|
177
|
+
event = self.class.aasm.state_machine.events[name]
|
178
|
+
event.fire_callbacks(:after_commit, self)
|
179
179
|
end
|
180
180
|
|
181
181
|
success
|
data/lib/aasm/state_machine.rb
CHANGED
data/lib/aasm/transition.rb
CHANGED
@@ -1,33 +1,35 @@
|
|
1
1
|
module AASM
|
2
2
|
class Transition
|
3
|
+
include DslHelper
|
4
|
+
|
3
5
|
attr_reader :from, :to, :opts
|
4
6
|
alias_method :options, :opts
|
5
7
|
|
6
|
-
def initialize(opts)
|
8
|
+
def initialize(opts, &block)
|
9
|
+
add_options_from_dsl(opts, [:on_transition, :guard, :after], &block) if block
|
10
|
+
|
7
11
|
@from = opts[:from]
|
8
12
|
@to = opts[:to]
|
9
|
-
@guards = Array(opts[:guard] || opts[:guards])
|
10
|
-
@
|
13
|
+
@guards = Array(opts[:guard] || opts[:guards] || opts[:if])
|
14
|
+
@unless = Array(opts[:unless]) #TODO: This could use a better name
|
15
|
+
|
16
|
+
if opts[:on_transition]
|
17
|
+
warn '[DEPRECATION] :on_transition is deprecated, use :after instead'
|
18
|
+
opts[:after] = Array(opts[:after]) + Array(opts[:on_transition])
|
19
|
+
end
|
20
|
+
@after = Array(opts[:after])
|
21
|
+
@after = @after[0] if @after.size == 1
|
22
|
+
|
11
23
|
@opts = opts
|
12
24
|
end
|
13
25
|
|
14
|
-
|
15
|
-
|
16
|
-
@
|
17
|
-
case guard
|
18
|
-
when Symbol, String
|
19
|
-
return false unless obj.send(guard, *args)
|
20
|
-
when Proc
|
21
|
-
return false unless guard.call(obj, *args)
|
22
|
-
end
|
23
|
-
end
|
24
|
-
true
|
26
|
+
def allowed?(obj, *args)
|
27
|
+
invoke_callbacks_compatible_with_guard(@guards, obj, args, :guard => true) &&
|
28
|
+
invoke_callbacks_compatible_with_guard(@unless, obj, args, :unless => true)
|
25
29
|
end
|
26
30
|
|
27
31
|
def execute(obj, *args)
|
28
|
-
@
|
29
|
-
@on_transition.each {|ot| _execute(obj, ot, *args)} :
|
30
|
-
_execute(obj, @on_transition, *args)
|
32
|
+
invoke_callbacks_compatible_with_guard(@after, obj, args)
|
31
33
|
end
|
32
34
|
|
33
35
|
def ==(obj)
|
@@ -40,15 +42,33 @@ module AASM
|
|
40
42
|
|
41
43
|
private
|
42
44
|
|
43
|
-
def
|
44
|
-
|
45
|
-
|
45
|
+
def invoke_callbacks_compatible_with_guard(code, record, args, options={})
|
46
|
+
if record.respond_to?(:aasm)
|
47
|
+
record.aasm.from_state = @from if record.aasm.respond_to?(:from_state=)
|
48
|
+
record.aasm.to_state = @to if record.aasm.respond_to?(:to_state=)
|
49
|
+
end
|
46
50
|
|
47
|
-
case
|
48
|
-
when Proc
|
49
|
-
on_transition.arity == 0 ? on_transition.call : on_transition.call(obj, *args)
|
51
|
+
case code
|
50
52
|
when Symbol, String
|
51
|
-
|
53
|
+
# QUESTION : record.send(code, *args) ?
|
54
|
+
arity = record.send(:method, code.to_sym).arity
|
55
|
+
arity == 0 ? record.send(code) : record.send(code, *args)
|
56
|
+
when Proc
|
57
|
+
# QUESTION : record.instance_exec(*args, &code) ?
|
58
|
+
code.arity == 0 ? record.instance_exec(&code) : record.instance_exec(*args, &code)
|
59
|
+
when Array
|
60
|
+
if options[:guard]
|
61
|
+
# invoke guard callbacks
|
62
|
+
code.all? {|a| invoke_callbacks_compatible_with_guard(a, record, args)}
|
63
|
+
elsif options[:unless]
|
64
|
+
# invoke unless callbacks
|
65
|
+
code.all? {|a| !invoke_callbacks_compatible_with_guard(a, record, args)}
|
66
|
+
else
|
67
|
+
# invoke after callbacks
|
68
|
+
code.map {|a| invoke_callbacks_compatible_with_guard(a, record, args)}
|
69
|
+
end
|
70
|
+
else
|
71
|
+
true
|
52
72
|
end
|
53
73
|
end
|
54
74
|
|
data/lib/aasm/version.rb
CHANGED
data/spec/database.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
ActiveRecord::Migration.suppress_messages do
|
2
|
-
%w{gates readers writers transients simples simple_new_dsls no_scopes thieves localizer_test_models persisted_states provided_and_persisted_states}.each do |table_name|
|
2
|
+
%w{gates readers writers transients simples simple_new_dsls no_scopes no_direct_assignments thieves localizer_test_models persisted_states provided_and_persisted_states}.each do |table_name|
|
3
3
|
ActiveRecord::Migration.create_table table_name, :force => true do |t|
|
4
4
|
t.string "aasm_state"
|
5
5
|
end
|
data/spec/models/auth_machine.rb
CHANGED
@@ -5,14 +5,18 @@ class AuthMachine
|
|
5
5
|
|
6
6
|
aasm do
|
7
7
|
state :passive
|
8
|
-
state :pending, :initial => true, :
|
9
|
-
state :active, :
|
8
|
+
state :pending, :initial => true, :before_enter => :make_activation_code
|
9
|
+
state :active, :before_enter => :do_activate
|
10
10
|
state :suspended
|
11
|
-
state :deleted, :
|
11
|
+
state :deleted, :before_enter => :do_delete#, :exit => :do_undelete
|
12
12
|
state :waiting
|
13
13
|
|
14
14
|
event :register do
|
15
|
-
transitions :from => :passive, :to => :pending
|
15
|
+
transitions :from => :passive, :to => :pending do
|
16
|
+
guard do
|
17
|
+
can_register?
|
18
|
+
end
|
19
|
+
end
|
16
20
|
end
|
17
21
|
|
18
22
|
event :activate do
|
@@ -33,8 +37,8 @@ class AuthMachine
|
|
33
37
|
end
|
34
38
|
|
35
39
|
event :unsuspend do
|
36
|
-
transitions :from => :suspended, :to => :active, :guard => Proc.new {
|
37
|
-
transitions :from => :suspended, :to => :pending, :guard =>
|
40
|
+
transitions :from => :suspended, :to => :active, :guard => Proc.new { has_activated? }
|
41
|
+
transitions :from => :suspended, :to => :pending, :guard => :has_activation_code?
|
38
42
|
transitions :from => :suspended, :to => :passive
|
39
43
|
end
|
40
44
|
|
@@ -1,9 +1,16 @@
|
|
1
1
|
class CallbackNewDsl
|
2
2
|
include AASM
|
3
3
|
|
4
|
+
def initialize(options={})
|
5
|
+
@fail_event_guard = options[:fail_event_guard]
|
6
|
+
@fail_transition_guard = options[:fail_transition_guard]
|
7
|
+
@log = options[:log]
|
8
|
+
end
|
9
|
+
|
4
10
|
aasm do
|
5
11
|
state :open, :initial => true,
|
6
12
|
:before_enter => :before_enter_open,
|
13
|
+
:enter => :enter_open,
|
7
14
|
:after_enter => :after_enter_open,
|
8
15
|
:before_exit => :before_exit_open,
|
9
16
|
:exit => :exit_open,
|
@@ -14,10 +21,62 @@ class CallbackNewDsl
|
|
14
21
|
:enter => :enter_closed,
|
15
22
|
:after_enter => :after_enter_closed,
|
16
23
|
:before_exit => :before_exit_closed,
|
24
|
+
:exit => :exit_closed,
|
25
|
+
:after_exit => :after_exit_closed
|
26
|
+
|
27
|
+
event :close, :before => :before, :after => :after, :guard => :event_guard do
|
28
|
+
transitions :to => :closed, :from => [:open], :guard => :transition_guard, :after => :transitioning
|
29
|
+
end
|
30
|
+
|
31
|
+
event :open, :before => :before, :after => :after do
|
32
|
+
transitions :to => :open, :from => :closed
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def log(text)
|
37
|
+
puts text if @log
|
38
|
+
end
|
39
|
+
|
40
|
+
def before_enter_open; log('before_enter_open'); end
|
41
|
+
def enter_open; log('enter_open'); end
|
42
|
+
def before_exit_open; log('before_exit_open'); end
|
43
|
+
def after_enter_open; log('after_enter_open'); end
|
44
|
+
def exit_open; log('exit_open'); end
|
45
|
+
def after_exit_open; log('after_exit_open'); end
|
46
|
+
|
47
|
+
def before_enter_closed; log('before_enter_closed'); end
|
48
|
+
def enter_closed; log('enter_closed'); end
|
49
|
+
def before_exit_closed; log('before_exit_closed'); end
|
50
|
+
def exit_closed; log('exit_closed'); end
|
51
|
+
def after_enter_closed; log('after_enter_closed'); end
|
52
|
+
def after_exit_closed; log('after_exit_closed'); end
|
53
|
+
|
54
|
+
def event_guard; log('event_guard'); !@fail_event_guard; end
|
55
|
+
def transition_guard; log('transition_guard'); !@fail_transition_guard; end
|
56
|
+
def transitioning; log('transitioning'); end
|
57
|
+
|
58
|
+
def before; log('before'); end
|
59
|
+
def after; log('after'); end
|
60
|
+
end
|
61
|
+
|
62
|
+
class CallbackNewDslArgs
|
63
|
+
include AASM
|
64
|
+
|
65
|
+
aasm do
|
66
|
+
state :open, :initial => true,
|
67
|
+
:before_enter => :before_enter_open,
|
68
|
+
:after_enter => :after_enter_open,
|
69
|
+
:before_exit => :before_exit_open,
|
70
|
+
:after_exit => :after_exit_open
|
71
|
+
|
72
|
+
state :closed,
|
73
|
+
:before_enter => :before_enter_closed,
|
74
|
+
:after_enter => :after_enter_closed,
|
75
|
+
:before_exit => :before_exit_closed,
|
17
76
|
:after_exit => :after_exit_closed
|
18
77
|
|
19
78
|
event :close, :before => :before, :after => :after do
|
20
|
-
transitions :to => :closed, :from => [:open]
|
79
|
+
transitions :to => :closed, :from => [:open], :after => :transition_proc
|
21
80
|
end
|
22
81
|
|
23
82
|
event :open, :before => :before, :after => :after do
|
@@ -25,19 +84,46 @@ class CallbackNewDsl
|
|
25
84
|
end
|
26
85
|
end
|
27
86
|
|
28
|
-
def
|
29
|
-
|
30
|
-
|
31
|
-
|
87
|
+
def log(text)
|
88
|
+
# puts text
|
89
|
+
end
|
90
|
+
|
91
|
+
def before_enter_open; log('before_enter_open'); end
|
92
|
+
def before_exit_open; log('before_exit_open'); end
|
93
|
+
def after_enter_open; log('after_enter_open'); end
|
94
|
+
def after_exit_open; log('after_exit_open'); end
|
95
|
+
|
96
|
+
def before_enter_closed; log('before_enter_closed'); end
|
97
|
+
def before_exit_closed; log('before_enter_closed'); end
|
98
|
+
def after_enter_closed; log('after_enter_closed'); end
|
99
|
+
def after_exit_closed; log('after_exit_closed'); end
|
100
|
+
|
101
|
+
def before(*args); log('before'); end
|
102
|
+
def transition_proc(arg1, arg2); log('transition_proc'); end
|
103
|
+
def after(*args); log('after'); end
|
104
|
+
end
|
105
|
+
|
106
|
+
class CallbackWithStateArg
|
107
|
+
|
108
|
+
include AASM
|
109
|
+
|
110
|
+
aasm do
|
111
|
+
state :open, :inital => true
|
112
|
+
state :closed
|
113
|
+
state :out_to_lunch
|
114
|
+
|
115
|
+
event :close, :before => :before_method, :after => :after_method do
|
116
|
+
transitions :to => :closed, :from => [:open], :after => :transition_method
|
117
|
+
transitions :to => :out_to_lunch, :from => [:open], :after => :transition_method2
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
def before_method(arg); end
|
122
|
+
|
123
|
+
def after_method(arg); end
|
32
124
|
|
33
|
-
def
|
34
|
-
def before_exit_closed; end
|
35
|
-
def after_enter_closed; end
|
36
|
-
def after_exit_closed; end
|
125
|
+
def transition_method(arg); end
|
37
126
|
|
38
|
-
def
|
39
|
-
def after; end
|
127
|
+
def transition_method2(arg); end
|
40
128
|
|
41
|
-
def enter_closed; end
|
42
|
-
def exit_open; end
|
43
129
|
end
|
@@ -10,9 +10,9 @@ class DoubleDefiner
|
|
10
10
|
end
|
11
11
|
|
12
12
|
# simulating a reload
|
13
|
-
state :finished, :
|
13
|
+
state :finished, :before_enter => :do_enter
|
14
14
|
event :finish do
|
15
|
-
transitions :from => :started, :to => :finished, :
|
15
|
+
transitions :from => :started, :to => :finished, :after => :do_on_transition
|
16
16
|
end
|
17
17
|
end
|
18
18
|
|
data/spec/models/foo.rb
CHANGED
@@ -1,8 +1,8 @@
|
|
1
1
|
class Foo
|
2
2
|
include AASM
|
3
3
|
aasm do
|
4
|
-
state :open, :initial => true, :
|
5
|
-
state :closed, :
|
4
|
+
state :open, :initial => true, :before_exit => :before_exit
|
5
|
+
state :closed, :before_enter => :before_enter
|
6
6
|
state :final
|
7
7
|
|
8
8
|
event :close, :success => :success_callback do
|
@@ -21,9 +21,9 @@ class Foo
|
|
21
21
|
def success_callback
|
22
22
|
end
|
23
23
|
|
24
|
-
def
|
24
|
+
def before_enter
|
25
25
|
end
|
26
|
-
def
|
26
|
+
def before_exit
|
27
27
|
end
|
28
28
|
end
|
29
29
|
|
@@ -12,9 +12,9 @@ class ParametrisedEvent
|
|
12
12
|
end
|
13
13
|
|
14
14
|
event :dress do
|
15
|
-
transitions :from => :sleeping, :to => :working, :
|
16
|
-
transitions :from => :showering, :to => [:working, :dating], :
|
17
|
-
transitions :from => :showering, :to => :prettying_up, :
|
15
|
+
transitions :from => :sleeping, :to => :working, :after => :wear_clothes
|
16
|
+
transitions :from => :showering, :to => [:working, :dating], :after => Proc.new { |*args| wear_clothes(*args) }
|
17
|
+
transitions :from => :showering, :to => :prettying_up, :after => [:condition_hair, :fix_hair]
|
18
18
|
end
|
19
19
|
end
|
20
20
|
|
data/spec/models/persistence.rb
CHANGED
@@ -48,7 +48,22 @@ end
|
|
48
48
|
class NoScope < ActiveRecord::Base
|
49
49
|
include AASM
|
50
50
|
aasm :create_scopes => false do
|
51
|
-
state :
|
51
|
+
state :pending, :initial => true
|
52
|
+
state :running
|
53
|
+
event :run do
|
54
|
+
transitions :from => :pending, :to => :running
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
class NoDirectAssignment < ActiveRecord::Base
|
60
|
+
include AASM
|
61
|
+
aasm :no_direct_assignment => true do
|
62
|
+
state :pending, :initial => true
|
63
|
+
state :running
|
64
|
+
event :run do
|
65
|
+
transitions :from => :pending, :to => :running
|
66
|
+
end
|
52
67
|
end
|
53
68
|
end
|
54
69
|
|
data/spec/models/validator.rb
CHANGED
@@ -1,12 +1,14 @@
|
|
1
1
|
require 'active_record'
|
2
2
|
|
3
3
|
class Validator < ActiveRecord::Base
|
4
|
+
|
4
5
|
include AASM
|
5
6
|
aasm :column => :status do
|
6
7
|
state :sleeping, :initial => true
|
7
|
-
state :running
|
8
|
-
state :failed, :after_enter => :fail
|
9
|
-
|
8
|
+
state :running
|
9
|
+
state :failed, :after_enter => :fail
|
10
|
+
|
11
|
+
event :run, :after_commit => :change_name! do
|
10
12
|
transitions :to => :running, :from => :sleeping
|
11
13
|
end
|
12
14
|
event :sleep do
|
@@ -16,6 +18,7 @@ class Validator < ActiveRecord::Base
|
|
16
18
|
transitions :to => :failed, :from => [:sleeping, :running]
|
17
19
|
end
|
18
20
|
end
|
21
|
+
|
19
22
|
validates_presence_of :name
|
20
23
|
|
21
24
|
def change_name!
|