state_machine 0.6.3 → 0.7.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.
- 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
|