state_machine 0.6.3 → 0.7.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.rdoc +31 -1
- data/README.rdoc +33 -21
- data/Rakefile +2 -2
- data/examples/merb-rest/controller.rb +51 -0
- data/examples/merb-rest/model.rb +28 -0
- data/examples/merb-rest/view_edit.html.erb +24 -0
- data/examples/merb-rest/view_index.html.erb +23 -0
- data/examples/merb-rest/view_new.html.erb +13 -0
- data/examples/merb-rest/view_show.html.erb +17 -0
- data/examples/rails-rest/controller.rb +43 -0
- data/examples/rails-rest/migration.rb +11 -0
- data/examples/rails-rest/model.rb +23 -0
- data/examples/rails-rest/view_edit.html.erb +25 -0
- data/examples/rails-rest/view_index.html.erb +23 -0
- data/examples/rails-rest/view_new.html.erb +14 -0
- data/examples/rails-rest/view_show.html.erb +17 -0
- data/lib/state_machine/assertions.rb +2 -2
- data/lib/state_machine/callback.rb +14 -8
- data/lib/state_machine/condition_proxy.rb +3 -3
- data/lib/state_machine/event.rb +19 -21
- data/lib/state_machine/event_collection.rb +114 -0
- data/lib/state_machine/extensions.rb +127 -11
- data/lib/state_machine/guard.rb +1 -1
- data/lib/state_machine/integrations/active_record/locale.rb +2 -1
- data/lib/state_machine/integrations/active_record.rb +117 -39
- data/lib/state_machine/integrations/data_mapper/observer.rb +20 -64
- data/lib/state_machine/integrations/data_mapper.rb +71 -26
- data/lib/state_machine/integrations/sequel.rb +69 -21
- data/lib/state_machine/machine.rb +267 -139
- data/lib/state_machine/machine_collection.rb +145 -0
- data/lib/state_machine/matcher.rb +2 -2
- data/lib/state_machine/node_collection.rb +9 -4
- data/lib/state_machine/state.rb +22 -32
- data/lib/state_machine/state_collection.rb +66 -17
- data/lib/state_machine/transition.rb +259 -28
- data/lib/state_machine.rb +121 -56
- data/tasks/state_machine.rake +1 -0
- data/tasks/state_machine.rb +26 -0
- data/test/active_record.log +116877 -0
- data/test/functional/state_machine_test.rb +118 -12
- data/test/sequel.log +28542 -0
- data/test/unit/callback_test.rb +46 -1
- data/test/unit/condition_proxy_test.rb +55 -28
- data/test/unit/event_collection_test.rb +228 -0
- data/test/unit/event_test.rb +51 -46
- data/test/unit/integrations/active_record_test.rb +128 -70
- data/test/unit/integrations/data_mapper_test.rb +150 -58
- data/test/unit/integrations/sequel_test.rb +63 -6
- data/test/unit/invalid_event_test.rb +7 -0
- data/test/unit/machine_collection_test.rb +678 -0
- data/test/unit/machine_test.rb +198 -91
- data/test/unit/node_collection_test.rb +33 -30
- data/test/unit/state_collection_test.rb +112 -5
- data/test/unit/state_test.rb +23 -3
- data/test/unit/transition_test.rb +750 -89
- metadata +28 -3
data/lib/state_machine/guard.rb
CHANGED
@@ -20,7 +20,7 @@ module StateMachine
|
|
20
20
|
# The requirement for verifying the event being guarded
|
21
21
|
attr_reader :event_requirement
|
22
22
|
|
23
|
-
# One or more
|
23
|
+
# One or more requirements for verifying the states being guarded. All
|
24
24
|
# requirements contain a mapping of {:from => matcher, :to => matcher}.
|
25
25
|
attr_reader :state_requirements
|
26
26
|
|
@@ -2,7 +2,8 @@
|
|
2
2
|
:activerecord => {
|
3
3
|
:errors => {
|
4
4
|
:messages => {
|
5
|
-
:
|
5
|
+
:invalid_event => StateMachine::Machine.default_messages[:invalid_event] % ['{{state}}'],
|
6
|
+
:invalid_transition => StateMachine::Machine.default_messages[:invalid_transition] % ['{{event}}']
|
6
7
|
}
|
7
8
|
}
|
8
9
|
}
|
@@ -33,6 +33,37 @@ module StateMachine
|
|
33
33
|
# vehicle.ignite # => true
|
34
34
|
# vehicle.reload # => #<Vehicle id: 1, name: "Ford Explorer", state: "idling">
|
35
35
|
#
|
36
|
+
# == Events
|
37
|
+
#
|
38
|
+
# As described in StateMachine::InstanceMethods#state_machine, event
|
39
|
+
# attributes are created for every machine that allow transitions to be
|
40
|
+
# performed automatically when the object's action (in this case, :save)
|
41
|
+
# is called.
|
42
|
+
#
|
43
|
+
# In ActiveRecord, these automated events are run in the following order:
|
44
|
+
# * before validation - Run before callbacks and persist new states, then validate
|
45
|
+
# * before save - If validation was skipped, run before callbacks and persist new states, then save
|
46
|
+
# * after save - Run after callbacks
|
47
|
+
#
|
48
|
+
# For example,
|
49
|
+
#
|
50
|
+
# vehicle = Vehicle.create # => #<Vehicle id: 1, name: nil, state: "parked">
|
51
|
+
# vehicle.state_event # => nil
|
52
|
+
# vehicle.state_event = 'invalid'
|
53
|
+
# vehicle.valid? # => false
|
54
|
+
# vehicle.errors.full_messages # => ["State event is invalid"]
|
55
|
+
#
|
56
|
+
# vehicle.state_event = 'ignite'
|
57
|
+
# vehicle.valid? # => true
|
58
|
+
# vehicle.save # => true
|
59
|
+
# vehicle.state # => "idling"
|
60
|
+
# vehicle.state_event # => nil
|
61
|
+
#
|
62
|
+
# Note that this can also be done on a mass-assignment basis:
|
63
|
+
#
|
64
|
+
# vehicle = Vehicle.create(:state_event => 'ignite') # => #<Vehicle id: 1, name: nil, state: "idling">
|
65
|
+
# vehicle.state # => "idling"
|
66
|
+
#
|
36
67
|
# == Transactions
|
37
68
|
#
|
38
69
|
# In order to ensure that any changes made during transition callbacks
|
@@ -60,6 +91,14 @@ module StateMachine
|
|
60
91
|
# rolled back. If an after callback halts the chain, the previous result
|
61
92
|
# still applies and the transaction is *not* rolled back.
|
62
93
|
#
|
94
|
+
# To turn off transactions:
|
95
|
+
#
|
96
|
+
# class Vehicle < ActiveRecord::Base
|
97
|
+
# state_machine :initial => :parked, :use_transactions => false do
|
98
|
+
# ...
|
99
|
+
# end
|
100
|
+
# end
|
101
|
+
#
|
63
102
|
# == Validation errors
|
64
103
|
#
|
65
104
|
# If an event fails to successfully fire because there are no matching
|
@@ -71,7 +110,7 @@ module StateMachine
|
|
71
110
|
#
|
72
111
|
# vehicle = Vehicle.create(:state => 'idling') # => #<Vehicle id: 1, name: nil, state: "idling">
|
73
112
|
# vehicle.ignite # => false
|
74
|
-
# vehicle.errors.full_messages # => ["State cannot
|
113
|
+
# vehicle.errors.full_messages # => ["State cannot transition via \"ignite\""]
|
75
114
|
#
|
76
115
|
# If an event fails to fire because of a validation error on the record and
|
77
116
|
# *not* because a matching transition was not available, no error messages
|
@@ -138,9 +177,21 @@ module StateMachine
|
|
138
177
|
# In addition to support for ActiveRecord-like hooks, there is additional
|
139
178
|
# support for ActiveRecord observers. Because of the way ActiveRecord
|
140
179
|
# observers are designed, there is less flexibility around the specific
|
141
|
-
# transitions that can be hooked in.
|
142
|
-
#
|
143
|
-
#
|
180
|
+
# transitions that can be hooked in. However, a large number of hooks
|
181
|
+
# *are* supported. For example, if a transition for a record's +state+
|
182
|
+
# attribute changes the state from +parked+ to +idling+ via the +ignite+
|
183
|
+
# event, the following observer methods are supported:
|
184
|
+
# * before/after_ignite_from_parked_to_idling
|
185
|
+
# * before/after_ignite_from_parked
|
186
|
+
# * before/after_ignite_to_idling
|
187
|
+
# * before/after_ignite
|
188
|
+
# * before/after_transition_state_from_parked_to_idling
|
189
|
+
# * before/after_transition_state_from_parked
|
190
|
+
# * before/after_transition_state_to_idling
|
191
|
+
# * before/after_transition_state
|
192
|
+
# * before/after_transition
|
193
|
+
#
|
194
|
+
# The following class shows an example of some of these hooks:
|
144
195
|
#
|
145
196
|
# class VehicleObserver < ActiveRecord::Observer
|
146
197
|
# def before_save(vehicle)
|
@@ -177,6 +228,10 @@ module StateMachine
|
|
177
228
|
# end
|
178
229
|
# end
|
179
230
|
module ActiveRecord
|
231
|
+
# The default options to use for state machines using this integration
|
232
|
+
class << self; attr_reader :defaults; end
|
233
|
+
@defaults = {:action => :save}
|
234
|
+
|
180
235
|
# Should this integration be used for state machines in the given class?
|
181
236
|
# Classes that inherit from ActiveRecord::Base will automatically use
|
182
237
|
# the ActiveRecord integration.
|
@@ -190,61 +245,57 @@ module StateMachine
|
|
190
245
|
I18n.load_path << "#{File.dirname(__FILE__)}/active_record/locale.rb" if Object.const_defined?(:I18n)
|
191
246
|
end
|
192
247
|
|
193
|
-
# Adds a validation error to the given object
|
194
|
-
|
195
|
-
def invalidate(object, event)
|
248
|
+
# Adds a validation error to the given object
|
249
|
+
def invalidate(object, attribute, message, values = [])
|
196
250
|
if Object.const_defined?(:I18n)
|
197
|
-
|
198
|
-
|
199
|
-
:
|
200
|
-
|
201
|
-
)
|
251
|
+
options = values.inject({}) {|options, (key, value)| options[key] = value; options}
|
252
|
+
object.errors.add(attribute, message, options.merge(
|
253
|
+
:default => @messages[message]
|
254
|
+
))
|
202
255
|
else
|
203
|
-
object.errors.add(attribute,
|
256
|
+
object.errors.add(attribute, generate_message(message, values))
|
204
257
|
end
|
205
258
|
end
|
206
259
|
|
207
|
-
# Resets
|
260
|
+
# Resets any errors previously added when invalidating the given object
|
208
261
|
def reset(object)
|
209
262
|
object.errors.clear
|
210
263
|
end
|
211
264
|
|
212
|
-
# Runs a new database transaction, rolling back any changes by raising
|
213
|
-
# an ActiveRecord::Rollback exception if the yielded block fails
|
214
|
-
# (i.e. returns false).
|
215
|
-
def within_transaction(object)
|
216
|
-
object.class.transaction {raise ::ActiveRecord::Rollback unless yield}
|
217
|
-
end
|
218
|
-
|
219
265
|
protected
|
220
266
|
# Adds the default callbacks for notifying ActiveRecord observers
|
221
267
|
# before/after a transition has been performed.
|
222
268
|
def after_initialize
|
223
|
-
# Observer callbacks never halt the chain; result is ignored
|
224
269
|
callbacks[:before] << Callback.new {|object, transition| notify(:before, object, transition)}
|
225
|
-
callbacks[:after] << Callback.new {|object, transition
|
226
|
-
end
|
227
|
-
|
228
|
-
# Sets the default action for all ActiveRecord state machines to +save+
|
229
|
-
def default_action
|
230
|
-
:save
|
270
|
+
callbacks[:after] << Callback.new {|object, transition| notify(:after, object, transition)}
|
231
271
|
end
|
232
272
|
|
233
273
|
# Skips defining reader/writer methods since this is done automatically
|
234
|
-
def
|
274
|
+
def define_state_accessor
|
235
275
|
end
|
236
276
|
|
237
277
|
# Adds support for defining the attribute predicate, while providing
|
238
278
|
# compatibility with the default predicate which determines whether
|
239
279
|
# *anything* is set for the attribute's value
|
240
|
-
def
|
280
|
+
def define_state_predicate
|
241
281
|
attribute = self.attribute
|
242
282
|
|
243
283
|
# Still use class_eval here instance of define_instance_method since
|
244
|
-
# we need to
|
245
|
-
|
284
|
+
# we need to be able to call +super+
|
285
|
+
@instance_helper_module.class_eval do
|
246
286
|
define_method("#{attribute}?") do |*args|
|
247
|
-
args.empty? ? super(*args) : self.class.
|
287
|
+
args.empty? ? super(*args) : self.class.state_machine(attribute).states.matches?(self, *args)
|
288
|
+
end
|
289
|
+
end
|
290
|
+
end
|
291
|
+
|
292
|
+
# Adds hooks into validation for automatically firing events
|
293
|
+
def define_action_helpers
|
294
|
+
if super && action == :save
|
295
|
+
@instance_helper_module.class_eval do
|
296
|
+
define_method(:valid?) do |*args|
|
297
|
+
self.class.state_machines.fire_attribute_events(self, :save, false) { super(*args) }
|
298
|
+
end
|
248
299
|
end
|
249
300
|
end
|
250
301
|
end
|
@@ -263,6 +314,13 @@ module StateMachine
|
|
263
314
|
define_scope(name, lambda {|values| {:conditions => ["#{attribute} NOT IN (?)", values]}})
|
264
315
|
end
|
265
316
|
|
317
|
+
# Runs a new database transaction, rolling back any changes by raising
|
318
|
+
# an ActiveRecord::Rollback exception if the yielded block fails
|
319
|
+
# (i.e. returns false).
|
320
|
+
def transaction(object)
|
321
|
+
object.class.transaction {raise ::ActiveRecord::Rollback unless yield}
|
322
|
+
end
|
323
|
+
|
266
324
|
# Creates a new callback in the callback chain, always inserting it
|
267
325
|
# before the default Observer callbacks that were created after
|
268
326
|
# initialization.
|
@@ -288,7 +346,7 @@ module StateMachine
|
|
288
346
|
# Created the scope and then override it with state translation
|
289
347
|
owner_class.named_scope(name)
|
290
348
|
owner_class.scopes[name] = lambda do |klass, *states|
|
291
|
-
machine_states = klass.
|
349
|
+
machine_states = klass.state_machine(attribute).states
|
292
350
|
values = states.flatten.map {|state| machine_states.fetch(state).value}
|
293
351
|
|
294
352
|
::ActiveRecord::NamedScope::Scope.new(klass, scope.call(values))
|
@@ -300,18 +358,38 @@ module StateMachine
|
|
300
358
|
# Notifies observers on the given object that a callback occurred
|
301
359
|
# involving the given transition. This will attempt to call the
|
302
360
|
# following methods on observers:
|
303
|
-
# * #{type}_#{
|
361
|
+
# * #{type}_#{qualified_event}_from_#{from}_to_#{to}
|
362
|
+
# * #{type}_#{qualified_event}_from_#{from}
|
363
|
+
# * #{type}_#{qualified_event}_to_#{to}
|
364
|
+
# * #{type}_#{qualified_event}
|
365
|
+
# * #{type}_transition_#{attribute}_from_#{from}_to_#{to}
|
366
|
+
# * #{type}_transition_#{attribute}_from_#{from}
|
367
|
+
# * #{type}_transition_#{attribute}_to_#{to}
|
368
|
+
# * #{type}_transition_#{attribute}
|
304
369
|
# * #{type}_transition
|
305
370
|
#
|
306
371
|
# This will always return true regardless of the results of the
|
307
372
|
# callbacks.
|
308
373
|
def notify(type, object, transition)
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
374
|
+
attribute = transition.attribute
|
375
|
+
event = transition.qualified_event
|
376
|
+
from = transition.from_name
|
377
|
+
to = transition.to_name
|
378
|
+
|
379
|
+
# Machine-specific updates
|
380
|
+
["#{type}_#{event}", "#{type}_transition_#{attribute}"].each do |event_segment|
|
381
|
+
["_from_#{from}", nil].each do |from_segment|
|
382
|
+
["_to_#{to}", nil].each do |to_segment|
|
383
|
+
object.class.changed
|
384
|
+
object.class.notify_observers([event_segment, from_segment, to_segment].join, object, transition)
|
385
|
+
end
|
386
|
+
end
|
313
387
|
end
|
314
388
|
|
389
|
+
# Generic updates
|
390
|
+
object.class.changed
|
391
|
+
object.class.notify_observers("#{type}_transition", object, transition)
|
392
|
+
|
315
393
|
true
|
316
394
|
end
|
317
395
|
end
|
@@ -2,7 +2,7 @@ module StateMachine
|
|
2
2
|
module Integrations #:nodoc:
|
3
3
|
module DataMapper
|
4
4
|
# Adds support for creating before/after transition callbacks within a
|
5
|
-
# DataMapper observer. These callbacks behave very
|
5
|
+
# DataMapper observer. These callbacks behave very similar to
|
6
6
|
# before/after hooks during save/update/destroy/etc., but with the
|
7
7
|
# following modifications:
|
8
8
|
# * Each callback can define a set of transition conditions (i.e. guards)
|
@@ -18,8 +18,8 @@ module StateMachine
|
|
18
18
|
#
|
19
19
|
# observe Vehicle, Switch, Project
|
20
20
|
#
|
21
|
-
# after_transition do |transition
|
22
|
-
# Audit.log(self, transition)
|
21
|
+
# after_transition do |transition|
|
22
|
+
# Audit.log(self, transition)
|
23
23
|
# end
|
24
24
|
# end
|
25
25
|
#
|
@@ -35,7 +35,6 @@ module StateMachine
|
|
35
35
|
# require 'dm-observer'
|
36
36
|
#
|
37
37
|
# If dm-observer is not available, then this feature will be skipped.
|
38
|
-
#
|
39
38
|
module Observer
|
40
39
|
include MatcherHelpers
|
41
40
|
|
@@ -96,59 +95,11 @@ module StateMachine
|
|
96
95
|
end
|
97
96
|
|
98
97
|
# Creates a callback that will be invoked *after* a transition is
|
99
|
-
# performed
|
100
|
-
# transition.
|
101
|
-
# must match in order for the callback to get invoked.
|
102
|
-
#
|
103
|
-
# See StateMachine::Machine#after_transition for more
|
104
|
-
# information about the various configuration options available.
|
105
|
-
#
|
106
|
-
# == Examples
|
107
|
-
#
|
108
|
-
# class Vehicle
|
109
|
-
# include DataMapper::Resource
|
110
|
-
#
|
111
|
-
# property :id, Serial
|
112
|
-
# property :state, :String
|
113
|
-
#
|
114
|
-
# state_machine :initial => :parked do
|
115
|
-
# event :ignite do
|
116
|
-
# transition :parked => :idling
|
117
|
-
# end
|
118
|
-
# end
|
119
|
-
# end
|
120
|
-
#
|
121
|
-
# class VehicleObserver
|
122
|
-
# include DataMapper::Observer
|
123
|
-
#
|
124
|
-
# observe Vehicle
|
125
|
-
#
|
126
|
-
# after :save do |saved|
|
127
|
-
# # log message
|
128
|
-
# end
|
129
|
-
#
|
130
|
-
# # Target all state machines
|
131
|
-
# after_transition :parked => :idling, :on => :ignite do
|
132
|
-
# # put on seatbelt
|
133
|
-
# end
|
134
|
-
#
|
135
|
-
# # Target a specific state machine
|
136
|
-
# after_transition :state, any => :idling do
|
137
|
-
# # put on seatbelt
|
138
|
-
# end
|
139
|
-
#
|
140
|
-
# # Target all state machines without requirements
|
141
|
-
# after_transition do |transition, saved|
|
142
|
-
# if saved
|
143
|
-
# # log message
|
144
|
-
# end
|
145
|
-
# end
|
146
|
-
# end
|
98
|
+
# performed so long as the given configuration options match the
|
99
|
+
# transition.
|
147
100
|
#
|
148
|
-
#
|
149
|
-
#
|
150
|
-
# Vehicle instance being transition). This means that +self+ refers
|
151
|
-
# to the vehicle record within each callback block.
|
101
|
+
# See +before_transition+ for a description of the possible configurations
|
102
|
+
# for defining callbacks.
|
152
103
|
def after_transition(*args, &block)
|
153
104
|
add_transition_callback(:after, *args, &block)
|
154
105
|
end
|
@@ -157,20 +108,25 @@ module StateMachine
|
|
157
108
|
# Adds the transition callback to a specific machine or all of the
|
158
109
|
# state machines for each observed class.
|
159
110
|
def add_transition_callback(type, *args, &block)
|
160
|
-
if args.
|
161
|
-
# Specific attribute
|
162
|
-
|
163
|
-
|
111
|
+
if args.any? && !args.first.is_a?(Hash)
|
112
|
+
# Specific attribute(s) being targeted
|
113
|
+
attributes = args
|
114
|
+
args = args.last.is_a?(Hash) ? [args.pop] : []
|
164
115
|
else
|
165
116
|
# Target all state machines
|
166
|
-
|
167
|
-
transition_args = args
|
117
|
+
attributes = nil
|
168
118
|
end
|
169
119
|
|
170
120
|
# Add the transition callback to each class being observed
|
171
121
|
observing.each do |klass|
|
172
|
-
state_machines =
|
173
|
-
|
122
|
+
state_machines =
|
123
|
+
if attributes
|
124
|
+
attributes.map {|attribute| klass.state_machines.fetch(attribute)}
|
125
|
+
else
|
126
|
+
klass.state_machines.values
|
127
|
+
end
|
128
|
+
|
129
|
+
state_machines.each {|machine| machine.send("#{type}_transition", *args, &block)}
|
174
130
|
end if observing
|
175
131
|
end
|
176
132
|
end
|
@@ -39,11 +39,43 @@ module StateMachine
|
|
39
39
|
# vehicle.ignite # => true
|
40
40
|
# vehicle.reload # => #<Vehicle id=1 name="Ford Explorer" state="idling">
|
41
41
|
#
|
42
|
+
# == Events
|
43
|
+
#
|
44
|
+
# As described in StateMachine::InstanceMethods#state_machine, event
|
45
|
+
# attributes are created for every machine that allow transitions to be
|
46
|
+
# performed automatically when the object's action (in this case, :save)
|
47
|
+
# is called.
|
48
|
+
#
|
49
|
+
# In DataMapper, these automated events are run in the following order:
|
50
|
+
# * before validation - If validation feature loaded, run before callbacks and persist new states, then validate
|
51
|
+
# * before save - If validation feature was skipped/not loaded, run before callbacks and persist new states, then save
|
52
|
+
# * after save - Run after callbacks
|
53
|
+
#
|
54
|
+
# For example,
|
55
|
+
#
|
56
|
+
# vehicle = Vehicle.create # => #<Vehicle id=1 name=nil state="parked">
|
57
|
+
# vehicle.state_event # => nil
|
58
|
+
# vehicle.state_event = 'invalid'
|
59
|
+
# vehicle.valid? # => false
|
60
|
+
# vehicle.errors # => #<DataMapper::Validate::ValidationErrors:0xb7a48b54 @errors={"state_event"=>["is invalid"]}>
|
61
|
+
#
|
62
|
+
# vehicle.state_event = 'ignite'
|
63
|
+
# vehicle.valid? # => true
|
64
|
+
# vehicle.save # => true
|
65
|
+
# vehicle.state # => "idling"
|
66
|
+
# vehicle.state_event # => nil
|
67
|
+
#
|
68
|
+
# Note that this can also be done on a mass-assignment basis:
|
69
|
+
#
|
70
|
+
# vehicle = Vehicle.create(:state_event => 'ignite') # => #<Vehicle id=1 name=nil state="idling">
|
71
|
+
# vehicle.state # => "idling"
|
72
|
+
#
|
42
73
|
# == Transactions
|
43
74
|
#
|
44
|
-
#
|
45
|
-
#
|
46
|
-
#
|
75
|
+
# By default, the use of transactions during an event transition is
|
76
|
+
# turned off to be consistent with DataMapper. This means that if
|
77
|
+
# changes are made to the database during a before callback, but the the
|
78
|
+
# transition fails to complete, those changes will *not* be rolled back.
|
47
79
|
#
|
48
80
|
# For example,
|
49
81
|
#
|
@@ -63,12 +95,15 @@ module StateMachine
|
|
63
95
|
#
|
64
96
|
# vehicle = Vehicle.create # => #<Vehicle id=1 name=nil state="parked">
|
65
97
|
# vehicle.ignite # => false
|
66
|
-
# Message.all.count # =>
|
98
|
+
# Message.all.count # => 1
|
67
99
|
#
|
68
|
-
#
|
69
|
-
#
|
70
|
-
#
|
71
|
-
#
|
100
|
+
# To turn on transactions:
|
101
|
+
#
|
102
|
+
# class Vehicle < ActiveRecord::Base
|
103
|
+
# state_machine :initial => :parked, :use_transactions => true do
|
104
|
+
# ...
|
105
|
+
# end
|
106
|
+
# end
|
72
107
|
#
|
73
108
|
# == Validation errors
|
74
109
|
#
|
@@ -81,7 +116,7 @@ module StateMachine
|
|
81
116
|
#
|
82
117
|
# vehicle = Vehicle.create(:state => 'idling') # => #<Vehicle id=1 name=nil state="idling">
|
83
118
|
# vehicle.ignite # => false
|
84
|
-
# vehicle.errors.full_messages # => ["cannot
|
119
|
+
# vehicle.errors.full_messages # => ["cannot transition via \"ignite\""]
|
85
120
|
#
|
86
121
|
# If an event fails to fire because of a validation error on the record and
|
87
122
|
# *not* because a matching transition was not available, no error messages
|
@@ -164,6 +199,10 @@ module StateMachine
|
|
164
199
|
# support for DataMapper observers. See StateMachine::Integrations::DataMapper::Observer
|
165
200
|
# for more information.
|
166
201
|
module DataMapper
|
202
|
+
# The default options to use for state machines using this integration
|
203
|
+
class << self; attr_reader :defaults; end
|
204
|
+
@defaults = {:action => :save, :use_transactions => false}
|
205
|
+
|
167
206
|
# Should this integration be used for state machines in the given class?
|
168
207
|
# Classes that include DataMapper::Resource will automatically use the
|
169
208
|
# DataMapper integration.
|
@@ -176,31 +215,31 @@ module StateMachine
|
|
176
215
|
require 'state_machine/integrations/data_mapper/observer' if ::DataMapper.const_defined?('Observer')
|
177
216
|
end
|
178
217
|
|
179
|
-
# Adds a validation error to the given object
|
180
|
-
|
181
|
-
|
182
|
-
object.errors.add(attribute, invalid_message(object, event)) if object.respond_to?(:errors)
|
218
|
+
# Adds a validation error to the given object
|
219
|
+
def invalidate(object, attribute, message, values = [])
|
220
|
+
object.errors.add(attribute, generate_message(message, values)) if object.respond_to?(:errors)
|
183
221
|
end
|
184
222
|
|
185
|
-
# Resets
|
223
|
+
# Resets any errors previously added when invalidating the given object
|
186
224
|
def reset(object)
|
187
|
-
object.errors.clear
|
188
|
-
end
|
189
|
-
|
190
|
-
# Runs a new database transaction, rolling back any changes if the
|
191
|
-
# yielded block fails (i.e. returns false).
|
192
|
-
def within_transaction(object)
|
193
|
-
object.class.transaction {|t| t.rollback unless yield}
|
225
|
+
object.errors.clear if object.respond_to?(:errors)
|
194
226
|
end
|
195
227
|
|
196
228
|
protected
|
197
|
-
#
|
198
|
-
def
|
199
|
-
|
229
|
+
# Skips defining reader/writer methods since this is done automatically
|
230
|
+
def define_state_accessor
|
231
|
+
owner_class.property(attribute, String) unless owner_class.properties.has_property?(attribute)
|
200
232
|
end
|
201
233
|
|
202
|
-
#
|
203
|
-
def
|
234
|
+
# Adds hooks into validation for automatically firing events
|
235
|
+
def define_action_helpers
|
236
|
+
if super && action == :save
|
237
|
+
@instance_helper_module.class_eval do
|
238
|
+
define_method(:valid?) do |*args|
|
239
|
+
self.class.state_machines.fire_attribute_events(self, :save, false) { super(*args) }
|
240
|
+
end
|
241
|
+
end
|
242
|
+
end
|
204
243
|
end
|
205
244
|
|
206
245
|
# Creates a scope for finding records *with* a particular state or
|
@@ -217,6 +256,12 @@ module StateMachine
|
|
217
256
|
lambda {|resource, values| resource.all(attribute.to_sym.not => values)}
|
218
257
|
end
|
219
258
|
|
259
|
+
# Runs a new database transaction, rolling back any changes if the
|
260
|
+
# yielded block fails (i.e. returns false).
|
261
|
+
def transaction(object)
|
262
|
+
object.class.transaction {|t| t.rollback unless yield}
|
263
|
+
end
|
264
|
+
|
220
265
|
# Creates a new callback in the callback chain, always ensuring that
|
221
266
|
# it's configured to bind to the object as this is the convention for
|
222
267
|
# DataMapper/Extlib callbacks
|