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/callbacks.txt
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
callbacks
|
2
|
+
|
3
|
+
AASM 3
|
4
|
+
|
5
|
+
begin
|
6
|
+
old_state exit # old? should be deprecated -> use old_state.before_exit instead
|
7
|
+
event before
|
8
|
+
old_state before_exit
|
9
|
+
new_state before_enter
|
10
|
+
new_state enter # old? should be deprecated -> use new_state.before_enter instead
|
11
|
+
...update state...
|
12
|
+
transition guard
|
13
|
+
event guard
|
14
|
+
transition on_transition
|
15
|
+
event success # if persist successful
|
16
|
+
old_state after_exit
|
17
|
+
new_state after_enter
|
18
|
+
event after
|
19
|
+
rescue
|
20
|
+
event error
|
21
|
+
end
|
22
|
+
|
23
|
+
AASM 4
|
24
|
+
|
25
|
+
todo
|
26
|
+
|
27
|
+
done
|
28
|
+
- move event.before before everything else
|
29
|
+
- move old_state.before_exit before old_state.exit
|
30
|
+
- move event.guard before transition.guard
|
31
|
+
- fire guards before running state callbacks (test run)
|
32
|
+
|
33
|
+
begin
|
34
|
+
event before
|
35
|
+
event guard # test run
|
36
|
+
transition guard # test run
|
37
|
+
old_state before_exit
|
38
|
+
old_state exit
|
39
|
+
new_state before_enter
|
40
|
+
new_state enter
|
41
|
+
event guard
|
42
|
+
transition guard
|
43
|
+
transition on_transition
|
44
|
+
...update state...
|
45
|
+
event success # if persist successful
|
46
|
+
old_state after_exit
|
47
|
+
new_state after_enter
|
48
|
+
event after
|
49
|
+
rescue
|
50
|
+
event error
|
51
|
+
end
|
data/lib/aasm.rb
CHANGED
data/lib/aasm/aasm.rb
CHANGED
@@ -26,59 +26,6 @@ module AASM
|
|
26
26
|
@aasm
|
27
27
|
end
|
28
28
|
|
29
|
-
# TODO remove this method in v4.0.0
|
30
|
-
def aasm_initial_state(set_state=nil)
|
31
|
-
if set_state
|
32
|
-
warn ".aasm_initial_state(:name) is deprecated and will be removed in version 4.0.0; please use .aasm.initial_state = :name instead!"
|
33
|
-
AASM::StateMachine[self].initial_state = set_state
|
34
|
-
else
|
35
|
-
warn ".aasm_initial_state is deprecated and will be removed in version 4.0.0; please use .aasm.initial_state instead!"
|
36
|
-
AASM::StateMachine[self].initial_state
|
37
|
-
end
|
38
|
-
end
|
39
|
-
|
40
|
-
# TODO remove this method in v4.0.0
|
41
|
-
def aasm_from_states_for_state(state, options={})
|
42
|
-
warn ".aasm_from_states_for_state is deprecated and will be removed in version 4.0.0; please use .aasm.from_states_for_state instead!"
|
43
|
-
aasm.from_states_for_state(state, options)
|
44
|
-
end
|
45
|
-
|
46
|
-
# TODO remove this method in v4.0.0
|
47
|
-
def aasm_initial_state=(state)
|
48
|
-
warn ".aasm_initial_state= is deprecated and will be removed in version 4.0.0"
|
49
|
-
AASM::StateMachine[self].initial_state = state
|
50
|
-
end
|
51
|
-
|
52
|
-
# TODO remove this method in v4.0.0
|
53
|
-
def aasm_state(name, options={})
|
54
|
-
warn ".aasm_state is deprecated and will be removed in version 4.0.0; please use .aasm.state instead!"
|
55
|
-
aasm.state(name, options)
|
56
|
-
end
|
57
|
-
|
58
|
-
# TODO remove this method in v4.0.0
|
59
|
-
def aasm_event(name, options = {}, &block)
|
60
|
-
warn ".aasm_event is deprecated and will be removed in version 4.0.0; please use .aasm.event instead!"
|
61
|
-
aasm.event(name, options, &block)
|
62
|
-
end
|
63
|
-
|
64
|
-
# TODO remove this method in v4.0.0
|
65
|
-
def aasm_states
|
66
|
-
warn ".aasm_states is deprecated and will be removed in version 4.0.0; please use .aasm.states instead!"
|
67
|
-
aasm.states
|
68
|
-
end
|
69
|
-
|
70
|
-
# TODO remove this method in v4.0.0
|
71
|
-
def aasm_events
|
72
|
-
warn ".aasm_events is deprecated and will be removed in version 4.0.0; please use .aasm.events instead!"
|
73
|
-
aasm.events
|
74
|
-
end
|
75
|
-
|
76
|
-
# TODO remove this method in v4.0.0
|
77
|
-
def aasm_states_for_select
|
78
|
-
warn ".aasm_states_for_select is deprecated and will be removed in version 4.0.0; please use .aasm.states_for_select instead!"
|
79
|
-
aasm.states_for_select
|
80
|
-
end
|
81
|
-
|
82
29
|
# aasm.event(:event_name).human?
|
83
30
|
def aasm_human_event_name(event) # event_name?
|
84
31
|
AASM::Localizer.new.human_event_name(self, event)
|
@@ -107,55 +54,43 @@ module AASM
|
|
107
54
|
true
|
108
55
|
end
|
109
56
|
|
110
|
-
|
111
|
-
def aasm_current_state
|
112
|
-
warn "#aasm_current_state is deprecated and will be removed in version 4.0.0; please use #aasm.current_state instead!"
|
113
|
-
aasm.current_state
|
114
|
-
end
|
115
|
-
|
116
|
-
# TODO remove this method in v4.0.0
|
117
|
-
def aasm_enter_initial_state
|
118
|
-
warn "#aasm_enter_initial_state is deprecated and will be removed in version 4.0.0; please use #aasm.enter_initial_state instead!"
|
119
|
-
aasm.enter_initial_state
|
120
|
-
end
|
121
|
-
|
122
|
-
# TODO remove this method in v4.0.0
|
123
|
-
def aasm_events_for_current_state
|
124
|
-
warn "#aasm_events_for_current_state is deprecated and will be removed in version 4.0.0; please use #aasm.events(aasm.current_state) instead!"
|
125
|
-
aasm.events(aasm.current_state)
|
126
|
-
end
|
127
|
-
|
128
|
-
# TODO remove this method in v4.0.0
|
129
|
-
def aasm_permissible_events_for_current_state
|
130
|
-
warn "#aasm_permissible_events_for_current_state is deprecated and will be removed in version 4.0.0; please use #aasm.permissible_events instead!"
|
131
|
-
aasm.permissible_events
|
132
|
-
end
|
133
|
-
|
134
|
-
# TODO remove this method in v4.0.0
|
135
|
-
def aasm_events_for_state(state_name)
|
136
|
-
warn "#aasm_events_for_state(state_name) is deprecated and will be removed in version 4.0.0; please use #aasm.events(state_name) instead!"
|
137
|
-
aasm.events(state_name)
|
138
|
-
end
|
57
|
+
private
|
139
58
|
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
59
|
+
# Takes args and a from state and removes the first
|
60
|
+
# element from args if it is a valid to_state for
|
61
|
+
# the event given the from_state
|
62
|
+
def process_args(event, from_state, *args)
|
63
|
+
# If the first arg doesn't respond to to_sym then
|
64
|
+
# it isn't a symbol or string so it can't be a state
|
65
|
+
# name anyway
|
66
|
+
return args unless args.first.respond_to?(:to_sym)
|
67
|
+
if event.transitions_from_state(from_state).map(&:to).flatten.include?(args.first)
|
68
|
+
return args[1..-1]
|
69
|
+
end
|
70
|
+
return args
|
144
71
|
end
|
145
72
|
|
146
|
-
private
|
147
|
-
|
148
73
|
def aasm_fire_event(event_name, options, *args, &block)
|
149
|
-
event = self.class.aasm.events[event_name]
|
74
|
+
event = self.class.aasm.state_machine.events[event_name]
|
150
75
|
begin
|
151
76
|
old_state = aasm.state_object_for_name(aasm.current_state)
|
152
|
-
old_state.fire_callbacks(:exit, self)
|
153
77
|
|
154
78
|
# new event before callback
|
155
|
-
event.fire_callbacks(
|
156
|
-
|
157
|
-
|
158
|
-
|
79
|
+
event.fire_callbacks(
|
80
|
+
:before,
|
81
|
+
self,
|
82
|
+
*process_args(event, aasm.current_state, *args)
|
83
|
+
)
|
84
|
+
|
85
|
+
if event.may_fire?(self, *args)
|
86
|
+
old_state.fire_callbacks(:before_exit, self)
|
87
|
+
old_state.fire_callbacks(:exit, self) # TODO: remove for AASM 4?
|
88
|
+
|
89
|
+
if new_state_name = event.fire(self, *args)
|
90
|
+
aasm_fired(event, old_state, new_state_name, options, *args, &block)
|
91
|
+
else
|
92
|
+
aasm_failed(event_name, old_state)
|
93
|
+
end
|
159
94
|
else
|
160
95
|
aasm_failed(event_name, old_state)
|
161
96
|
end
|
@@ -164,16 +99,14 @@ private
|
|
164
99
|
end
|
165
100
|
end
|
166
101
|
|
167
|
-
def aasm_fired(event, old_state, new_state_name, options)
|
102
|
+
def aasm_fired(event, old_state, new_state_name, options, *args)
|
168
103
|
persist = options[:persist]
|
169
104
|
|
170
105
|
new_state = aasm.state_object_for_name(new_state_name)
|
171
106
|
|
172
|
-
# new before_ callbacks
|
173
|
-
old_state.fire_callbacks(:before_exit, self)
|
174
107
|
new_state.fire_callbacks(:before_enter, self)
|
175
108
|
|
176
|
-
new_state.fire_callbacks(:enter, self)
|
109
|
+
new_state.fire_callbacks(:enter, self) # TODO: remove for AASM 4?
|
177
110
|
|
178
111
|
persist_successful = true
|
179
112
|
if persist
|
@@ -190,7 +123,11 @@ private
|
|
190
123
|
if persist_successful
|
191
124
|
old_state.fire_callbacks(:after_exit, self)
|
192
125
|
new_state.fire_callbacks(:after_enter, self)
|
193
|
-
event.fire_callbacks(
|
126
|
+
event.fire_callbacks(
|
127
|
+
:after,
|
128
|
+
self,
|
129
|
+
*process_args(event, old_state.name, *args)
|
130
|
+
)
|
194
131
|
|
195
132
|
self.aasm_event_fired(event.name, old_state.name, aasm.current_state) if self.respond_to?(:aasm_event_fired)
|
196
133
|
else
|
data/lib/aasm/base.rb
CHANGED
@@ -1,10 +1,13 @@
|
|
1
1
|
module AASM
|
2
2
|
class Base
|
3
3
|
|
4
|
+
attr_reader :state_machine
|
5
|
+
|
4
6
|
def initialize(klass, options={}, &block)
|
5
7
|
@klass = klass
|
6
8
|
@state_machine = AASM::StateMachine[@klass]
|
7
|
-
@state_machine.config.column
|
9
|
+
@state_machine.config.column ||= (options[:column] || :aasm_state).to_sym # aasm4
|
10
|
+
# @state_machine.config.column = options[:column].to_sym if options[:column] # master
|
8
11
|
@options = options
|
9
12
|
|
10
13
|
# let's cry if the transition is invalid
|
@@ -13,13 +16,22 @@ module AASM
|
|
13
16
|
# create named scopes for each state
|
14
17
|
configure :create_scopes, true
|
15
18
|
|
16
|
-
# don't store any new state if the model is invalid
|
19
|
+
# don't store any new state if the model is invalid (in ActiveRecord)
|
17
20
|
configure :skip_validation_on_save, false
|
18
21
|
|
19
|
-
# use requires_new for nested transactions
|
22
|
+
# use requires_new for nested transactions (in ActiveRecord)
|
20
23
|
configure :requires_new_transaction, true
|
21
24
|
|
25
|
+
# set to true to forbid direct assignment of aasm_state column (in ActiveRecord)
|
26
|
+
configure :no_direct_assignment, false
|
27
|
+
|
22
28
|
configure :enum, nil
|
29
|
+
|
30
|
+
if @state_machine.config.no_direct_assignment
|
31
|
+
@klass.send(:define_method, "#{@state_machine.config.column}=") do |state_name|
|
32
|
+
raise AASM::NoDirectAssignmentError.new('direct assignment of AASM column has been disabled (see AASM configuration for this class)')
|
33
|
+
end
|
34
|
+
end
|
23
35
|
end
|
24
36
|
|
25
37
|
def initial_state(new_initial_state=nil)
|
@@ -70,7 +82,7 @@ module AASM
|
|
70
82
|
end
|
71
83
|
|
72
84
|
def events
|
73
|
-
@state_machine.events
|
85
|
+
@state_machine.events.values
|
74
86
|
end
|
75
87
|
|
76
88
|
def states_for_select
|
@@ -79,16 +91,15 @@ module AASM
|
|
79
91
|
|
80
92
|
def from_states_for_state(state, options={})
|
81
93
|
if options[:transition]
|
82
|
-
events[options[:transition]].transitions_to_state(state).flatten.map(&:from).flatten
|
94
|
+
@state_machine.events[options[:transition]].transitions_to_state(state).flatten.map(&:from).flatten
|
83
95
|
else
|
84
|
-
events.map {|
|
96
|
+
events.map {|e| e.transitions_to_state(state)}.flatten.map(&:from).flatten
|
85
97
|
end
|
86
98
|
end
|
87
99
|
|
88
100
|
private
|
89
101
|
|
90
102
|
def configure(key, default_value)
|
91
|
-
@state_machine.config.send(:new_ostruct_member, key)
|
92
103
|
if @options.key?(key)
|
93
104
|
@state_machine.config.send("#{key}=", @options[key])
|
94
105
|
elsif @state_machine.config.send(key).nil?
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module AASM
|
2
|
+
class Configuration
|
3
|
+
# for all persistence layers: which database column to use?
|
4
|
+
attr_accessor :column
|
5
|
+
|
6
|
+
# let's cry if the transition is invalid
|
7
|
+
attr_accessor :whiny_transitions
|
8
|
+
|
9
|
+
# for all persistence layers: create named scopes for each state
|
10
|
+
attr_accessor :create_scopes
|
11
|
+
|
12
|
+
# for ActiveRecord: don't store any new state if the model is invalid
|
13
|
+
attr_accessor :skip_validation_on_save
|
14
|
+
|
15
|
+
# for ActiveRecord: use requires_new for nested transactions?
|
16
|
+
attr_accessor :requires_new_transaction
|
17
|
+
|
18
|
+
# forbid direct assignment in aasm_state column (in ActiveRecord)
|
19
|
+
attr_accessor :no_direct_assignment
|
20
|
+
|
21
|
+
attr_accessor :enum
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module DslHelper
|
2
|
+
|
3
|
+
class Proxy
|
4
|
+
attr_accessor :options
|
5
|
+
|
6
|
+
def initialize(options, valid_keys, source)
|
7
|
+
@valid_keys = valid_keys
|
8
|
+
@source = source
|
9
|
+
|
10
|
+
@options = options
|
11
|
+
end
|
12
|
+
|
13
|
+
def method_missing(name, *args, &block)
|
14
|
+
if @valid_keys.include?(name)
|
15
|
+
options[name] = Array(options[name])
|
16
|
+
options[name] << block if block
|
17
|
+
options[name] += Array(args)
|
18
|
+
else
|
19
|
+
@source.send name, *args, &block
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def add_options_from_dsl(options, valid_keys, &block)
|
25
|
+
proxy = Proxy.new(options, valid_keys, self)
|
26
|
+
proxy.instance_eval(&block)
|
27
|
+
proxy.options
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
data/lib/aasm/errors.rb
CHANGED
data/lib/aasm/event.rb
CHANGED
@@ -1,13 +1,18 @@
|
|
1
1
|
module AASM
|
2
2
|
class Event
|
3
|
+
include DslHelper
|
3
4
|
|
4
5
|
attr_reader :name, :options
|
5
6
|
|
6
7
|
def initialize(name, options = {}, &block)
|
7
8
|
@name = name
|
8
9
|
@transitions = []
|
9
|
-
@guards = Array(options[:guard] || options[:guards])
|
10
|
-
|
10
|
+
@guards = Array(options[:guard] || options[:guards] || options[:if])
|
11
|
+
@unless = Array(options[:unless]) #TODO: This could use a better name
|
12
|
+
|
13
|
+
# from aasm4
|
14
|
+
@options = options # QUESTION: .dup ?
|
15
|
+
add_options_from_dsl(@options, [:after, :before, :error, :success], &block) if block
|
11
16
|
end
|
12
17
|
|
13
18
|
# a neutered version of fire - it doesn't actually fire the event, it just
|
@@ -37,13 +42,9 @@ module AASM
|
|
37
42
|
@transitions.select { |t| t.to == state }
|
38
43
|
end
|
39
44
|
|
40
|
-
# TODO remove this method in v4.0.0
|
41
|
-
def all_transitions
|
42
|
-
warn "Event#all_transitions is deprecated and will be removed in version 4.0.0; please use Event#transitions instead!"
|
43
|
-
transitions
|
44
|
-
end
|
45
|
-
|
46
45
|
def fire_callbacks(callback_name, record, *args)
|
46
|
+
# strip out the first element in args if it's a valid to_state
|
47
|
+
# #given where we're coming from, this condition implies args not empty
|
47
48
|
invoke_callbacks(@options[callback_name], record, args)
|
48
49
|
end
|
49
50
|
|
@@ -74,18 +75,14 @@ module AASM
|
|
74
75
|
|
75
76
|
def attach_event_guards(definitions)
|
76
77
|
unless @guards.empty?
|
77
|
-
given_guards = Array(definitions.delete(:guard) || definitions.delete(:guards))
|
78
|
-
definitions[:guards] =
|
78
|
+
given_guards = Array(definitions.delete(:guard) || definitions.delete(:guards) || definitions.delete(:if))
|
79
|
+
definitions[:guards] = @guards + given_guards # from aasm4
|
79
80
|
end
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
def update(options = {}, &block)
|
84
|
-
@options = options
|
85
|
-
if block then
|
86
|
-
instance_eval(&block)
|
81
|
+
unless @unless.empty?
|
82
|
+
given_unless = Array(definitions.delete(:unless))
|
83
|
+
definitions[:unless] = given_unless + @unless
|
87
84
|
end
|
88
|
-
|
85
|
+
definitions
|
89
86
|
end
|
90
87
|
|
91
88
|
# Execute if test == false, otherwise return true/false depending on whether it would fire
|
@@ -98,9 +95,18 @@ module AASM
|
|
98
95
|
transitions = @transitions
|
99
96
|
end
|
100
97
|
|
98
|
+
# If to_state is not nil it either contains a potential
|
99
|
+
# to_state or an arg
|
100
|
+
unless to_state == nil
|
101
|
+
if !to_state.respond_to?(:to_sym) || !transitions.map(&:to).flatten.include?(to_state.to_sym)
|
102
|
+
args.unshift(to_state)
|
103
|
+
to_state = nil
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
101
107
|
transitions.each do |transition|
|
102
108
|
next if to_state and !Array(transition.to).include?(to_state)
|
103
|
-
if transition.
|
109
|
+
if transition.allowed?(obj, *args)
|
104
110
|
if test
|
105
111
|
result = true
|
106
112
|
else
|
@@ -130,12 +136,5 @@ module AASM
|
|
130
136
|
end
|
131
137
|
end
|
132
138
|
|
133
|
-
[:after, :before, :error, :success].each do |callback_name|
|
134
|
-
define_method callback_name do |*args, &block|
|
135
|
-
options[callback_name] = Array(options[callback_name])
|
136
|
-
options[callback_name] << block if block
|
137
|
-
options[callback_name] += Array(args)
|
138
|
-
end
|
139
|
-
end
|
140
139
|
end
|
141
140
|
end # AASM
|