aasm 3.4.0 → 4.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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!
|