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/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
|