aasm 3.0.24 → 3.4.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 +6 -0
- data/.travis.yml +29 -4
- data/CHANGELOG.md +56 -0
- data/Gemfile +10 -1
- data/LICENSE +1 -1
- data/README.md +151 -20
- data/aasm.gemspec +5 -6
- data/gemfiles/rails_3.2.gemfile +13 -0
- data/gemfiles/rails_4.0.gemfile +16 -0
- data/gemfiles/rails_4.1.gemfile +16 -0
- data/lib/aasm/aasm.rb +36 -32
- data/lib/aasm/base.rb +49 -31
- data/lib/aasm/event.rb +28 -17
- data/lib/aasm/instance_base.rb +9 -4
- data/lib/aasm/localizer.rb +1 -1
- data/lib/aasm/persistence/active_record_persistence.rb +65 -16
- data/lib/aasm/persistence/base.rb +10 -14
- data/lib/aasm/persistence/mongoid_persistence.rb +10 -8
- data/lib/aasm/persistence/sequel_persistence.rb +108 -0
- data/lib/aasm/persistence.rb +3 -0
- data/lib/aasm/state.rb +4 -3
- data/lib/aasm/state_machine.rb +18 -10
- data/lib/aasm/transition.rb +13 -6
- data/lib/aasm/version.rb +1 -1
- data/lib/aasm.rb +0 -3
- data/spec/database.rb +33 -0
- data/spec/models/double_definer.rb +21 -0
- data/spec/models/foo.rb +2 -1
- data/spec/models/guardian.rb +48 -0
- data/spec/models/mongoid/no_scope_mongoid.rb +1 -1
- data/spec/models/mongoid/simple_mongoid.rb +5 -4
- data/spec/models/mongoid/simple_new_dsl_mongoid.rb +1 -1
- data/spec/models/not_auto_loaded/process.rb +10 -8
- data/spec/models/persistence.rb +5 -13
- data/spec/spec_helper.rb +1 -1
- data/spec/unit/api_spec.rb +12 -12
- data/spec/unit/callbacks_spec.rb +29 -45
- data/spec/unit/complex_example_spec.rb +24 -15
- data/spec/unit/event_naming_spec.rb +24 -0
- data/spec/unit/event_spec.rb +124 -76
- data/spec/unit/guard_spec.rb +60 -0
- data/spec/unit/initial_state_spec.rb +4 -5
- data/spec/unit/inspection_spec.rb +42 -53
- data/spec/unit/localizer_spec.rb +22 -18
- data/spec/unit/memory_leak_spec.rb +2 -2
- data/spec/unit/new_dsl_spec.rb +2 -2
- data/spec/unit/persistence/active_record_persistence_spec.rb +357 -89
- data/spec/unit/persistence/mongoid_persistance_spec.rb +102 -81
- data/spec/unit/persistence/sequel_persistence_spec.rb +103 -0
- data/spec/unit/reloading_spec.rb +15 -0
- data/spec/unit/simple_example_spec.rb +20 -21
- data/spec/unit/state_spec.rb +16 -16
- data/spec/unit/subclassing_spec.rb +8 -8
- data/spec/unit/transition_spec.rb +59 -44
- metadata +38 -96
- data/lib/aasm/deprecated/aasm.rb +0 -15
- data/spec/models/callback_old_dsl.rb +0 -41
- data/spec/schema.rb +0 -35
data/lib/aasm/aasm.rb
CHANGED
@@ -5,7 +5,7 @@ module AASM
|
|
5
5
|
|
6
6
|
# do not overwrite existing state machines, which could have been created by
|
7
7
|
# inheritance, see class method inherited
|
8
|
-
AASM::StateMachine[base] ||= AASM::StateMachine.new
|
8
|
+
AASM::StateMachine[base] ||= AASM::StateMachine.new
|
9
9
|
|
10
10
|
AASM::Persistence.load_persistence(base)
|
11
11
|
super
|
@@ -26,52 +26,56 @@ module AASM
|
|
26
26
|
@aasm
|
27
27
|
end
|
28
28
|
|
29
|
-
# TODO
|
29
|
+
# TODO remove this method in v4.0.0
|
30
30
|
def aasm_initial_state(set_state=nil)
|
31
31
|
if set_state
|
32
|
-
|
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
33
|
AASM::StateMachine[self].initial_state = set_state
|
34
34
|
else
|
35
|
+
warn ".aasm_initial_state is deprecated and will be removed in version 4.0.0; please use .aasm.initial_state instead!"
|
35
36
|
AASM::StateMachine[self].initial_state
|
36
37
|
end
|
37
38
|
end
|
38
39
|
|
39
|
-
#
|
40
|
+
# TODO remove this method in v4.0.0
|
40
41
|
def aasm_from_states_for_state(state, options={})
|
41
|
-
|
42
|
-
|
43
|
-
else
|
44
|
-
aasm.events.map {|k,v| v.transitions_to_state(state)}.flatten.map(&:from).flatten
|
45
|
-
end
|
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)
|
46
44
|
end
|
47
45
|
|
48
|
-
#
|
46
|
+
# TODO remove this method in v4.0.0
|
49
47
|
def aasm_initial_state=(state)
|
48
|
+
warn ".aasm_initial_state= is deprecated and will be removed in version 4.0.0"
|
50
49
|
AASM::StateMachine[self].initial_state = state
|
51
50
|
end
|
52
51
|
|
53
|
-
#
|
52
|
+
# TODO remove this method in v4.0.0
|
54
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
55
|
aasm.state(name, options)
|
56
56
|
end
|
57
57
|
|
58
|
-
#
|
58
|
+
# TODO remove this method in v4.0.0
|
59
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!"
|
60
61
|
aasm.event(name, options, &block)
|
61
62
|
end
|
62
63
|
|
63
|
-
#
|
64
|
+
# TODO remove this method in v4.0.0
|
64
65
|
def aasm_states
|
66
|
+
warn ".aasm_states is deprecated and will be removed in version 4.0.0; please use .aasm.states instead!"
|
65
67
|
aasm.states
|
66
68
|
end
|
67
69
|
|
68
|
-
#
|
70
|
+
# TODO remove this method in v4.0.0
|
69
71
|
def aasm_events
|
72
|
+
warn ".aasm_events is deprecated and will be removed in version 4.0.0; please use .aasm.events instead!"
|
70
73
|
aasm.events
|
71
74
|
end
|
72
75
|
|
73
|
-
#
|
76
|
+
# TODO remove this method in v4.0.0
|
74
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!"
|
75
79
|
aasm.states_for_select
|
76
80
|
end
|
77
81
|
|
@@ -103,46 +107,46 @@ module AASM
|
|
103
107
|
true
|
104
108
|
end
|
105
109
|
|
106
|
-
|
110
|
+
# TODO remove this method in v4.0.0
|
107
111
|
def aasm_current_state
|
108
|
-
|
112
|
+
warn "#aasm_current_state is deprecated and will be removed in version 4.0.0; please use #aasm.current_state instead!"
|
109
113
|
aasm.current_state
|
110
114
|
end
|
111
115
|
|
112
|
-
|
116
|
+
# TODO remove this method in v4.0.0
|
113
117
|
def aasm_enter_initial_state
|
114
|
-
|
118
|
+
warn "#aasm_enter_initial_state is deprecated and will be removed in version 4.0.0; please use #aasm.enter_initial_state instead!"
|
115
119
|
aasm.enter_initial_state
|
116
120
|
end
|
117
121
|
|
118
|
-
|
122
|
+
# TODO remove this method in v4.0.0
|
119
123
|
def aasm_events_for_current_state
|
120
|
-
|
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!"
|
121
125
|
aasm.events(aasm.current_state)
|
122
126
|
end
|
123
127
|
|
124
|
-
|
128
|
+
# TODO remove this method in v4.0.0
|
125
129
|
def aasm_permissible_events_for_current_state
|
126
|
-
|
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!"
|
127
131
|
aasm.permissible_events
|
128
132
|
end
|
129
133
|
|
130
|
-
|
134
|
+
# TODO remove this method in v4.0.0
|
131
135
|
def aasm_events_for_state(state_name)
|
132
|
-
|
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!"
|
133
137
|
aasm.events(state_name)
|
134
138
|
end
|
135
139
|
|
136
|
-
|
140
|
+
# TODO remove this method in v4.0.0
|
137
141
|
def aasm_human_state
|
138
|
-
|
142
|
+
warn "#aasm_human_state is deprecated and will be removed in version 4.0.0; please use #aasm.human_state instead!"
|
139
143
|
aasm.human_state
|
140
144
|
end
|
141
145
|
|
142
146
|
private
|
143
147
|
|
144
148
|
def aasm_fire_event(event_name, options, *args, &block)
|
145
|
-
event = self.class.
|
149
|
+
event = self.class.aasm.events[event_name]
|
146
150
|
begin
|
147
151
|
old_state = aasm.state_object_for_name(aasm.current_state)
|
148
152
|
old_state.fire_callbacks(:exit, self)
|
@@ -151,16 +155,16 @@ private
|
|
151
155
|
event.fire_callbacks(:before, self)
|
152
156
|
|
153
157
|
if new_state_name = event.fire(self, *args)
|
154
|
-
|
158
|
+
aasm_fired(event, old_state, new_state_name, options, &block)
|
155
159
|
else
|
156
|
-
|
160
|
+
aasm_failed(event_name, old_state)
|
157
161
|
end
|
158
162
|
rescue StandardError => e
|
159
163
|
event.fire_callbacks(:error, self, e) || raise(e)
|
160
164
|
end
|
161
165
|
end
|
162
166
|
|
163
|
-
def
|
167
|
+
def aasm_fired(event, old_state, new_state_name, options)
|
164
168
|
persist = options[:persist]
|
165
169
|
|
166
170
|
new_state = aasm.state_object_for_name(new_state_name)
|
@@ -196,7 +200,7 @@ private
|
|
196
200
|
persist_successful
|
197
201
|
end
|
198
202
|
|
199
|
-
def
|
203
|
+
def aasm_failed(event_name, old_state)
|
200
204
|
if self.respond_to?(:aasm_event_failed)
|
201
205
|
self.aasm_event_failed(event_name, old_state.name)
|
202
206
|
end
|
data/lib/aasm/base.rb
CHANGED
@@ -1,67 +1,66 @@
|
|
1
1
|
module AASM
|
2
2
|
class Base
|
3
3
|
|
4
|
-
def initialize(
|
5
|
-
@
|
6
|
-
@state_machine = AASM::StateMachine[@
|
4
|
+
def initialize(klass, options={}, &block)
|
5
|
+
@klass = klass
|
6
|
+
@state_machine = AASM::StateMachine[@klass]
|
7
7
|
@state_machine.config.column = options[:column].to_sym if options[:column]
|
8
|
+
@options = options
|
8
9
|
|
9
|
-
if
|
10
|
-
|
11
|
-
elsif @state_machine.config.whiny_transitions.nil?
|
12
|
-
@state_machine.config.whiny_transitions = true # this is the default, so let's cry
|
13
|
-
end
|
10
|
+
# let's cry if the transition is invalid
|
11
|
+
configure :whiny_transitions, true
|
14
12
|
|
15
|
-
|
16
|
-
|
17
|
-
elsif @state_machine.config.create_scopes.nil?
|
18
|
-
@state_machine.config.create_scopes = true # this is the default, so let's create scopes
|
19
|
-
end
|
13
|
+
# create named scopes for each state
|
14
|
+
configure :create_scopes, true
|
20
15
|
|
21
|
-
if
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
16
|
+
# don't store any new state if the model is invalid
|
17
|
+
configure :skip_validation_on_save, false
|
18
|
+
|
19
|
+
# use requires_new for nested transactions
|
20
|
+
configure :requires_new_transaction, true
|
21
|
+
|
22
|
+
configure :enum, nil
|
26
23
|
end
|
27
24
|
|
28
|
-
def initial_state
|
29
|
-
|
25
|
+
def initial_state(new_initial_state=nil)
|
26
|
+
if new_initial_state
|
27
|
+
@state_machine.initial_state = new_initial_state
|
28
|
+
else
|
29
|
+
@state_machine.initial_state
|
30
|
+
end
|
30
31
|
end
|
31
32
|
|
32
33
|
# define a state
|
33
34
|
def state(name, options={})
|
34
|
-
|
35
|
-
@state_machine.add_state(name, @clazz, options)
|
36
|
-
@state_machine.initial_state = name if options[:initial] || !@state_machine.initial_state
|
35
|
+
@state_machine.add_state(name, @klass, options)
|
37
36
|
|
38
|
-
@
|
37
|
+
@klass.send(:define_method, "#{name.to_s}?") do
|
39
38
|
aasm.current_state == name
|
40
39
|
end
|
41
40
|
|
42
|
-
unless @
|
43
|
-
@
|
41
|
+
unless @klass.const_defined?("STATE_#{name.to_s.upcase}")
|
42
|
+
@klass.const_set("STATE_#{name.to_s.upcase}", name)
|
44
43
|
end
|
45
44
|
end
|
46
45
|
|
47
46
|
# define an event
|
48
47
|
def event(name, options={}, &block)
|
49
|
-
# @clazz.aasm_event(name, options, &block)
|
50
|
-
|
51
48
|
@state_machine.events[name] = AASM::Event.new(name, options, &block)
|
52
49
|
|
53
50
|
# an addition over standard aasm so that, before firing an event, you can ask
|
54
51
|
# may_event? and get back a boolean that tells you whether the guard method
|
55
52
|
# on the transition will let this happen.
|
56
|
-
@
|
53
|
+
@klass.send(:define_method, "may_#{name.to_s}?") do |*args|
|
57
54
|
aasm.may_fire_event?(name, *args)
|
58
55
|
end
|
59
56
|
|
60
|
-
@
|
57
|
+
@klass.send(:define_method, "#{name.to_s}!") do |*args, &block|
|
58
|
+
aasm.current_event = "#{name.to_s}!".to_sym
|
61
59
|
aasm_fire_event(name, {:persist => true}, *args, &block)
|
62
60
|
end
|
63
61
|
|
64
|
-
@
|
62
|
+
@klass.send(:define_method, "#{name.to_s}") do |*args, &block|
|
63
|
+
aasm.current_event = name.to_sym
|
65
64
|
aasm_fire_event(name, {:persist => false}, *args, &block)
|
66
65
|
end
|
67
66
|
end
|
@@ -78,5 +77,24 @@ module AASM
|
|
78
77
|
states.map { |state| state.for_select }
|
79
78
|
end
|
80
79
|
|
80
|
+
def from_states_for_state(state, options={})
|
81
|
+
if options[:transition]
|
82
|
+
events[options[:transition]].transitions_to_state(state).flatten.map(&:from).flatten
|
83
|
+
else
|
84
|
+
events.map {|k,v| v.transitions_to_state(state)}.flatten.map(&:from).flatten
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
private
|
89
|
+
|
90
|
+
def configure(key, default_value)
|
91
|
+
@state_machine.config.send(:new_ostruct_member, key)
|
92
|
+
if @options.key?(key)
|
93
|
+
@state_machine.config.send("#{key}=", @options[key])
|
94
|
+
elsif @state_machine.config.send(key).nil?
|
95
|
+
@state_machine.config.send("#{key}=", default_value)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
81
99
|
end
|
82
100
|
end
|
data/lib/aasm/event.rb
CHANGED
@@ -6,6 +6,7 @@ module AASM
|
|
6
6
|
def initialize(name, options = {}, &block)
|
7
7
|
@name = name
|
8
8
|
@transitions = []
|
9
|
+
@guards = Array(options[:guard] || options[:guards])
|
9
10
|
update(options, &block)
|
10
11
|
end
|
11
12
|
|
@@ -25,7 +26,7 @@ module AASM
|
|
25
26
|
end
|
26
27
|
|
27
28
|
def transitions_from_state(state)
|
28
|
-
@transitions.select { |t| t.from == state }
|
29
|
+
@transitions.select { |t| t.from.nil? or t.from == state }
|
29
30
|
end
|
30
31
|
|
31
32
|
def transitions_to_state?(state)
|
@@ -36,9 +37,9 @@ module AASM
|
|
36
37
|
@transitions.select { |t| t.to == state }
|
37
38
|
end
|
38
39
|
|
39
|
-
#
|
40
|
+
# TODO remove this method in v4.0.0
|
40
41
|
def all_transitions
|
41
|
-
|
42
|
+
warn "Event#all_transitions is deprecated and will be removed in version 4.0.0; please use Event#transitions instead!"
|
42
43
|
transitions
|
43
44
|
end
|
44
45
|
|
@@ -54,8 +55,31 @@ module AASM
|
|
54
55
|
end
|
55
56
|
end
|
56
57
|
|
58
|
+
## DSL interface
|
59
|
+
def transitions(definitions=nil)
|
60
|
+
if definitions # define new transitions
|
61
|
+
# Create a separate transition for each from-state to the given state
|
62
|
+
Array(definitions[:from]).each do |s|
|
63
|
+
@transitions << AASM::Transition.new(attach_event_guards(definitions.merge(:from => s.to_sym)))
|
64
|
+
end
|
65
|
+
# Create a transition if :to is specified without :from (transitions from ANY state)
|
66
|
+
if @transitions.empty? && definitions[:to]
|
67
|
+
@transitions << AASM::Transition.new(attach_event_guards(definitions))
|
68
|
+
end
|
69
|
+
end
|
70
|
+
@transitions
|
71
|
+
end
|
72
|
+
|
57
73
|
private
|
58
74
|
|
75
|
+
def attach_event_guards(definitions)
|
76
|
+
unless @guards.empty?
|
77
|
+
given_guards = Array(definitions.delete(:guard) || definitions.delete(:guards))
|
78
|
+
definitions[:guards] = given_guards + @guards
|
79
|
+
end
|
80
|
+
definitions
|
81
|
+
end
|
82
|
+
|
59
83
|
def update(options = {}, &block)
|
60
84
|
@options = options
|
61
85
|
if block then
|
@@ -68,7 +92,7 @@ module AASM
|
|
68
92
|
def _fire(obj, test, to_state=nil, *args)
|
69
93
|
result = test ? false : nil
|
70
94
|
if @transitions.map(&:from).any?
|
71
|
-
transitions = @transitions.select { |t| t.from == obj.
|
95
|
+
transitions = @transitions.select { |t| t.from == obj.aasm.current_state }
|
72
96
|
return result if transitions.size == 0
|
73
97
|
else
|
74
98
|
transitions = @transitions
|
@@ -106,19 +130,6 @@ module AASM
|
|
106
130
|
end
|
107
131
|
end
|
108
132
|
|
109
|
-
## DSL interface
|
110
|
-
def transitions(trans_opts=nil)
|
111
|
-
if trans_opts # define new transitions
|
112
|
-
# Create a separate transition for each from state to the given state
|
113
|
-
Array(trans_opts[:from]).each do |s|
|
114
|
-
@transitions << AASM::Transition.new(trans_opts.merge({:from => s.to_sym}))
|
115
|
-
end
|
116
|
-
# Create a transition if to is specified without from (transitions from ANY state)
|
117
|
-
@transitions << AASM::Transition.new(trans_opts) if @transitions.empty? && trans_opts[:to]
|
118
|
-
end
|
119
|
-
@transitions
|
120
|
-
end
|
121
|
-
|
122
133
|
[:after, :before, :error, :success].each do |callback_name|
|
123
134
|
define_method callback_name do |*args, &block|
|
124
135
|
options[callback_name] = Array(options[callback_name])
|
data/lib/aasm/instance_base.rb
CHANGED
@@ -1,6 +1,8 @@
|
|
1
1
|
module AASM
|
2
2
|
class InstanceBase
|
3
3
|
|
4
|
+
attr_accessor :from_state, :to_state, :current_event
|
5
|
+
|
4
6
|
def initialize(instance)
|
5
7
|
@instance = instance
|
6
8
|
end
|
@@ -15,7 +17,7 @@ module AASM
|
|
15
17
|
end
|
16
18
|
|
17
19
|
def enter_initial_state
|
18
|
-
state_name = determine_state_name(@instance.class.
|
20
|
+
state_name = determine_state_name(@instance.class.aasm.initial_state)
|
19
21
|
state_object = state_object_for_name(state_name)
|
20
22
|
|
21
23
|
state_object.fire_callbacks(:before_enter, @instance)
|
@@ -33,7 +35,7 @@ module AASM
|
|
33
35
|
def states(options={})
|
34
36
|
if options[:permissible]
|
35
37
|
# ugliness level 1000
|
36
|
-
transitions = @instance.class.aasm.events.
|
38
|
+
transitions = @instance.class.aasm.events.values_at(*permissible_events).compact.map {|e| e.transitions_from_state(current_state) }
|
37
39
|
tos = transitions.map {|t| t[0] ? t[0].to : nil}.flatten.compact.map(&:to_sym).uniq
|
38
40
|
@instance.class.aasm.states.select {|s| tos.include?(s.name.to_sym)}
|
39
41
|
else
|
@@ -73,8 +75,11 @@ module AASM
|
|
73
75
|
end
|
74
76
|
|
75
77
|
def may_fire_event?(name, *args)
|
76
|
-
event = @instance.class.aasm.events[name]
|
77
|
-
|
78
|
+
if event = @instance.class.aasm.events[name]
|
79
|
+
event.may_fire?(@instance, *args)
|
80
|
+
else
|
81
|
+
false # unknown event
|
82
|
+
end
|
78
83
|
end
|
79
84
|
|
80
85
|
def set_current_state_with_persistence(state)
|
data/lib/aasm/localizer.rb
CHANGED
@@ -47,7 +47,7 @@ module AASM
|
|
47
47
|
|
48
48
|
def ancestors_list(klass)
|
49
49
|
klass.ancestors.select do |ancestor|
|
50
|
-
ancestor.respond_to?(:model_name) unless ancestor == ActiveRecord::Base
|
50
|
+
ancestor.respond_to?(:model_name) unless ancestor.name == 'ActiveRecord::Base'
|
51
51
|
end
|
52
52
|
end
|
53
53
|
end
|
@@ -36,6 +36,12 @@ module AASM
|
|
36
36
|
else
|
37
37
|
base.before_validation_on_create(:aasm_ensure_initial_state)
|
38
38
|
end
|
39
|
+
|
40
|
+
# ensure initial aasm state even when validations are skipped
|
41
|
+
base.before_create(:aasm_ensure_initial_state)
|
42
|
+
|
43
|
+
# ensure state is in the list of states
|
44
|
+
base.validate :aasm_validate_states
|
39
45
|
end
|
40
46
|
|
41
47
|
module ClassMethods
|
@@ -71,18 +77,19 @@ module AASM
|
|
71
77
|
# Writes <tt>state</tt> to the state column and persists it to the database
|
72
78
|
#
|
73
79
|
# foo = Foo.find(1)
|
74
|
-
# foo.
|
80
|
+
# foo.aasm.current_state # => :opened
|
75
81
|
# foo.close!
|
76
|
-
# foo.
|
77
|
-
# Foo.find(1).
|
82
|
+
# foo.aasm.current_state # => :closed
|
83
|
+
# Foo.find(1).aasm.current_state # => :closed
|
78
84
|
#
|
79
85
|
# NOTE: intended to be called from an event
|
80
86
|
def aasm_write_state(state)
|
81
87
|
old_value = read_attribute(self.class.aasm_column)
|
82
|
-
|
88
|
+
aasm_write_attribute state
|
83
89
|
|
84
|
-
success = if
|
85
|
-
|
90
|
+
success = if aasm_skipping_validations
|
91
|
+
value = aasm_raw_attribute_value state
|
92
|
+
self.class.where(self.class.primary_key => self.id).update_all(self.class.aasm_column => value) == 1
|
86
93
|
else
|
87
94
|
self.save
|
88
95
|
end
|
@@ -97,20 +104,52 @@ module AASM
|
|
97
104
|
# Writes <tt>state</tt> to the state column, but does not persist it to the database
|
98
105
|
#
|
99
106
|
# foo = Foo.find(1)
|
100
|
-
# foo.
|
107
|
+
# foo.aasm.current_state # => :opened
|
101
108
|
# foo.close
|
102
|
-
# foo.
|
103
|
-
# Foo.find(1).
|
109
|
+
# foo.aasm.current_state # => :closed
|
110
|
+
# Foo.find(1).aasm.current_state # => :opened
|
104
111
|
# foo.save
|
105
|
-
# foo.
|
106
|
-
# Foo.find(1).
|
112
|
+
# foo.aasm.current_state # => :closed
|
113
|
+
# Foo.find(1).aasm.current_state # => :closed
|
107
114
|
#
|
108
115
|
# NOTE: intended to be called from an event
|
109
116
|
def aasm_write_state_without_persistence(state)
|
110
|
-
|
117
|
+
aasm_write_attribute state
|
111
118
|
end
|
112
119
|
|
113
120
|
private
|
121
|
+
def aasm_enum
|
122
|
+
case AASM::StateMachine[self.class].config.enum
|
123
|
+
when false then nil
|
124
|
+
when true then aasm_guess_enum_method
|
125
|
+
when nil then aasm_guess_enum_method if aasm_column_looks_like_enum
|
126
|
+
else AASM::StateMachine[self.class].config.enum
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
def aasm_column_looks_like_enum
|
131
|
+
self.class.columns_hash[self.class.aasm_column.to_s].type == :integer
|
132
|
+
end
|
133
|
+
|
134
|
+
def aasm_guess_enum_method
|
135
|
+
self.class.aasm_column.to_s.pluralize.to_sym
|
136
|
+
end
|
137
|
+
|
138
|
+
def aasm_skipping_validations
|
139
|
+
AASM::StateMachine[self.class].config.skip_validation_on_save
|
140
|
+
end
|
141
|
+
|
142
|
+
def aasm_write_attribute(state)
|
143
|
+
write_attribute self.class.aasm_column, aasm_raw_attribute_value(state)
|
144
|
+
end
|
145
|
+
|
146
|
+
def aasm_raw_attribute_value(state)
|
147
|
+
if aasm_enum
|
148
|
+
value = self.class.send(aasm_enum)[state]
|
149
|
+
else
|
150
|
+
value = state.to_s
|
151
|
+
end
|
152
|
+
end
|
114
153
|
|
115
154
|
# Ensures that if the aasm_state column is nil and the record is new
|
116
155
|
# that the initial state gets populated before validation on create
|
@@ -132,17 +171,27 @@ module AASM
|
|
132
171
|
end
|
133
172
|
|
134
173
|
def aasm_fire_event(name, options, *args, &block)
|
135
|
-
success = self.class.transaction(:requires_new =>
|
136
|
-
super
|
137
|
-
end
|
174
|
+
success = options[:persist] ? self.class.transaction(:requires_new => requires_new?) { super } : super
|
138
175
|
|
139
|
-
if success
|
176
|
+
if success && options[:persist]
|
140
177
|
new_state = aasm.state_object_for_name(aasm.current_state)
|
141
178
|
new_state.fire_callbacks(:after_commit, self)
|
142
179
|
end
|
143
180
|
|
144
181
|
success
|
145
182
|
end
|
183
|
+
|
184
|
+
def requires_new?
|
185
|
+
AASM::StateMachine[self.class].config.requires_new_transaction
|
186
|
+
end
|
187
|
+
|
188
|
+
def aasm_validate_states
|
189
|
+
unless AASM::StateMachine[self.class].config.skip_validation_on_save
|
190
|
+
if aasm.current_state && !aasm.states.include?(aasm.current_state)
|
191
|
+
self.errors.add(AASM::StateMachine[self.class].config.column , "is invalid")
|
192
|
+
end
|
193
|
+
end
|
194
|
+
end
|
146
195
|
end # InstanceMethods
|
147
196
|
|
148
197
|
end
|
@@ -35,7 +35,7 @@ module AASM
|
|
35
35
|
def aasm_read_state
|
36
36
|
state = send(self.class.aasm_column)
|
37
37
|
if new_record?
|
38
|
-
state.blank? ? aasm.determine_state_name(self.class.
|
38
|
+
state.blank? ? aasm.determine_state_name(self.class.aasm.initial_state) : state.to_sym
|
39
39
|
else
|
40
40
|
state.nil? ? nil : state.to_sym
|
41
41
|
end
|
@@ -87,26 +87,22 @@ module AASM
|
|
87
87
|
# make sure to create a (named) scope for each state
|
88
88
|
def state_with_scope(name, *args)
|
89
89
|
state_without_scope(name, *args)
|
90
|
-
if AASM::StateMachine[@
|
91
|
-
if @
|
90
|
+
if AASM::StateMachine[@klass].config.create_scopes && !@klass.respond_to?(name)
|
91
|
+
if @klass.ancestors.map {|klass| klass.to_s}.include?("ActiveRecord::Base")
|
92
92
|
|
93
|
-
conditions = {"#{@
|
94
|
-
if ActiveRecord::VERSION::MAJOR >=
|
95
|
-
@
|
93
|
+
conditions = {"#{@klass.table_name}.#{@klass.aasm_column}" => name.to_s}
|
94
|
+
if ActiveRecord::VERSION::MAJOR >= 3
|
95
|
+
@klass.class_eval do
|
96
96
|
scope name, lambda { where(conditions) }
|
97
97
|
end
|
98
|
-
elsif ActiveRecord::VERSION::MAJOR >= 3
|
99
|
-
@clazz.class_eval do
|
100
|
-
scope name, where(conditions)
|
101
|
-
end
|
102
98
|
else
|
103
|
-
@
|
99
|
+
@klass.class_eval do
|
104
100
|
named_scope name, :conditions => conditions
|
105
101
|
end
|
106
102
|
end
|
107
|
-
elsif @
|
108
|
-
scope_options = lambda { @
|
109
|
-
@
|
103
|
+
elsif @klass.ancestors.map {|klass| klass.to_s}.include?("Mongoid::Document")
|
104
|
+
scope_options = lambda { @klass.send(:where, {@klass.aasm_column.to_sym => name.to_s}) }
|
105
|
+
@klass.send(:scope, name, scope_options)
|
110
106
|
end
|
111
107
|
end
|
112
108
|
end
|
@@ -36,6 +36,8 @@ module AASM
|
|
36
36
|
# Mongoid's Validatable gem dependency goes not have a before_validation_on_xxx hook yet.
|
37
37
|
# base.before_validation_on_create :aasm_ensure_initial_state
|
38
38
|
base.before_validation :aasm_ensure_initial_state
|
39
|
+
# ensure initial aasm state even when validations are skipped
|
40
|
+
base.before_create :aasm_ensure_initial_state
|
39
41
|
end
|
40
42
|
|
41
43
|
module ClassMethods
|
@@ -66,10 +68,10 @@ module AASM
|
|
66
68
|
# using update_attribute (which bypasses validation)
|
67
69
|
#
|
68
70
|
# foo = Foo.find(1)
|
69
|
-
# foo.
|
71
|
+
# foo.aasm.current_state # => :opened
|
70
72
|
# foo.close!
|
71
|
-
# foo.
|
72
|
-
# Foo.find(1).
|
73
|
+
# foo.aasm.current_state # => :closed
|
74
|
+
# Foo.find(1).aasm.current_state # => :closed
|
73
75
|
#
|
74
76
|
# NOTE: intended to be called from an event
|
75
77
|
def aasm_write_state(state)
|
@@ -87,13 +89,13 @@ module AASM
|
|
87
89
|
# Writes <tt>state</tt> to the state column, but does not persist it to the database
|
88
90
|
#
|
89
91
|
# foo = Foo.find(1)
|
90
|
-
# foo.
|
92
|
+
# foo.aasm.current_state # => :opened
|
91
93
|
# foo.close
|
92
|
-
# foo.
|
93
|
-
# Foo.find(1).
|
94
|
+
# foo.aasm.current_state # => :closed
|
95
|
+
# Foo.find(1).aasm.current_state # => :opened
|
94
96
|
# foo.save
|
95
|
-
# foo.
|
96
|
-
# Foo.find(1).
|
97
|
+
# foo.aasm.current_state # => :closed
|
98
|
+
# Foo.find(1).aasm.current_state # => :closed
|
97
99
|
#
|
98
100
|
# NOTE: intended to be called from an event
|
99
101
|
def aasm_write_state_without_persistence(state)
|