spree-state_machine 2.0.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (140) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +8 -0
  3. data/.travis.yml +12 -0
  4. data/.yardopts +5 -0
  5. data/CHANGELOG.md +502 -0
  6. data/Gemfile +3 -0
  7. data/LICENSE +20 -0
  8. data/README.md +1246 -0
  9. data/Rakefile +20 -0
  10. data/examples/AutoShop_state.png +0 -0
  11. data/examples/Car_state.png +0 -0
  12. data/examples/Gemfile +5 -0
  13. data/examples/Gemfile.lock +14 -0
  14. data/examples/TrafficLight_state.png +0 -0
  15. data/examples/Vehicle_state.png +0 -0
  16. data/examples/auto_shop.rb +13 -0
  17. data/examples/car.rb +21 -0
  18. data/examples/doc/AutoShop.html +2856 -0
  19. data/examples/doc/AutoShop_state.png +0 -0
  20. data/examples/doc/Car.html +919 -0
  21. data/examples/doc/Car_state.png +0 -0
  22. data/examples/doc/TrafficLight.html +2230 -0
  23. data/examples/doc/TrafficLight_state.png +0 -0
  24. data/examples/doc/Vehicle.html +7921 -0
  25. data/examples/doc/Vehicle_state.png +0 -0
  26. data/examples/doc/_index.html +136 -0
  27. data/examples/doc/class_list.html +47 -0
  28. data/examples/doc/css/common.css +1 -0
  29. data/examples/doc/css/full_list.css +55 -0
  30. data/examples/doc/css/style.css +322 -0
  31. data/examples/doc/file_list.html +46 -0
  32. data/examples/doc/frames.html +13 -0
  33. data/examples/doc/index.html +136 -0
  34. data/examples/doc/js/app.js +205 -0
  35. data/examples/doc/js/full_list.js +173 -0
  36. data/examples/doc/js/jquery.js +16 -0
  37. data/examples/doc/method_list.html +734 -0
  38. data/examples/doc/top-level-namespace.html +105 -0
  39. data/examples/merb-rest/controller.rb +51 -0
  40. data/examples/merb-rest/model.rb +28 -0
  41. data/examples/merb-rest/view_edit.html.erb +24 -0
  42. data/examples/merb-rest/view_index.html.erb +23 -0
  43. data/examples/merb-rest/view_new.html.erb +13 -0
  44. data/examples/merb-rest/view_show.html.erb +17 -0
  45. data/examples/rails-rest/controller.rb +43 -0
  46. data/examples/rails-rest/migration.rb +7 -0
  47. data/examples/rails-rest/model.rb +23 -0
  48. data/examples/rails-rest/view__form.html.erb +34 -0
  49. data/examples/rails-rest/view_edit.html.erb +6 -0
  50. data/examples/rails-rest/view_index.html.erb +25 -0
  51. data/examples/rails-rest/view_new.html.erb +5 -0
  52. data/examples/rails-rest/view_show.html.erb +19 -0
  53. data/examples/traffic_light.rb +9 -0
  54. data/examples/vehicle.rb +33 -0
  55. data/lib/state_machine/assertions.rb +36 -0
  56. data/lib/state_machine/branch.rb +225 -0
  57. data/lib/state_machine/callback.rb +236 -0
  58. data/lib/state_machine/core.rb +7 -0
  59. data/lib/state_machine/core_ext/class/state_machine.rb +5 -0
  60. data/lib/state_machine/core_ext.rb +2 -0
  61. data/lib/state_machine/error.rb +13 -0
  62. data/lib/state_machine/eval_helpers.rb +87 -0
  63. data/lib/state_machine/event.rb +257 -0
  64. data/lib/state_machine/event_collection.rb +141 -0
  65. data/lib/state_machine/extensions.rb +149 -0
  66. data/lib/state_machine/graph.rb +92 -0
  67. data/lib/state_machine/helper_module.rb +17 -0
  68. data/lib/state_machine/initializers/rails.rb +25 -0
  69. data/lib/state_machine/initializers.rb +4 -0
  70. data/lib/state_machine/integrations/active_model/locale.rb +11 -0
  71. data/lib/state_machine/integrations/active_model/observer.rb +33 -0
  72. data/lib/state_machine/integrations/active_model/observer_update.rb +42 -0
  73. data/lib/state_machine/integrations/active_model/versions.rb +31 -0
  74. data/lib/state_machine/integrations/active_model.rb +585 -0
  75. data/lib/state_machine/integrations/active_record/locale.rb +20 -0
  76. data/lib/state_machine/integrations/active_record/versions.rb +123 -0
  77. data/lib/state_machine/integrations/active_record.rb +525 -0
  78. data/lib/state_machine/integrations/base.rb +100 -0
  79. data/lib/state_machine/integrations.rb +121 -0
  80. data/lib/state_machine/machine.rb +2287 -0
  81. data/lib/state_machine/machine_collection.rb +74 -0
  82. data/lib/state_machine/macro_methods.rb +522 -0
  83. data/lib/state_machine/matcher.rb +123 -0
  84. data/lib/state_machine/matcher_helpers.rb +54 -0
  85. data/lib/state_machine/node_collection.rb +222 -0
  86. data/lib/state_machine/path.rb +120 -0
  87. data/lib/state_machine/path_collection.rb +90 -0
  88. data/lib/state_machine/state.rb +297 -0
  89. data/lib/state_machine/state_collection.rb +112 -0
  90. data/lib/state_machine/state_context.rb +138 -0
  91. data/lib/state_machine/transition.rb +470 -0
  92. data/lib/state_machine/transition_collection.rb +245 -0
  93. data/lib/state_machine/version.rb +3 -0
  94. data/lib/state_machine/yard/handlers/base.rb +32 -0
  95. data/lib/state_machine/yard/handlers/event.rb +25 -0
  96. data/lib/state_machine/yard/handlers/machine.rb +344 -0
  97. data/lib/state_machine/yard/handlers/state.rb +25 -0
  98. data/lib/state_machine/yard/handlers/transition.rb +47 -0
  99. data/lib/state_machine/yard/handlers.rb +12 -0
  100. data/lib/state_machine/yard/templates/default/class/html/setup.rb +30 -0
  101. data/lib/state_machine/yard/templates/default/class/html/state_machines.erb +12 -0
  102. data/lib/state_machine/yard/templates.rb +3 -0
  103. data/lib/state_machine/yard.rb +8 -0
  104. data/lib/state_machine.rb +8 -0
  105. data/lib/yard-state_machine.rb +2 -0
  106. data/state_machine.gemspec +22 -0
  107. data/test/files/en.yml +17 -0
  108. data/test/files/switch.rb +15 -0
  109. data/test/functional/state_machine_test.rb +1066 -0
  110. data/test/test_helper.rb +7 -0
  111. data/test/unit/assertions_test.rb +40 -0
  112. data/test/unit/branch_test.rb +969 -0
  113. data/test/unit/callback_test.rb +704 -0
  114. data/test/unit/error_test.rb +43 -0
  115. data/test/unit/eval_helpers_test.rb +270 -0
  116. data/test/unit/event_collection_test.rb +398 -0
  117. data/test/unit/event_test.rb +1196 -0
  118. data/test/unit/graph_test.rb +98 -0
  119. data/test/unit/helper_module_test.rb +17 -0
  120. data/test/unit/integrations/active_model_test.rb +1245 -0
  121. data/test/unit/integrations/active_record_test.rb +2551 -0
  122. data/test/unit/integrations/base_test.rb +104 -0
  123. data/test/unit/integrations_test.rb +71 -0
  124. data/test/unit/invalid_event_test.rb +20 -0
  125. data/test/unit/invalid_parallel_transition_test.rb +18 -0
  126. data/test/unit/invalid_transition_test.rb +115 -0
  127. data/test/unit/machine_collection_test.rb +603 -0
  128. data/test/unit/machine_test.rb +3395 -0
  129. data/test/unit/matcher_helpers_test.rb +37 -0
  130. data/test/unit/matcher_test.rb +155 -0
  131. data/test/unit/node_collection_test.rb +362 -0
  132. data/test/unit/path_collection_test.rb +266 -0
  133. data/test/unit/path_test.rb +485 -0
  134. data/test/unit/state_collection_test.rb +352 -0
  135. data/test/unit/state_context_test.rb +441 -0
  136. data/test/unit/state_machine_test.rb +31 -0
  137. data/test/unit/state_test.rb +1101 -0
  138. data/test/unit/transition_collection_test.rb +2168 -0
  139. data/test/unit/transition_test.rb +1558 -0
  140. metadata +264 -0
data/README.md ADDED
@@ -0,0 +1,1246 @@
1
+ # spree-state_machine [![Build Status](https://secure.travis-ci.org/spree-contrib/state_machine.png "Build Status")](http://travis-ci.org/spree-contrib/state_machine)
2
+
3
+ **NOTE: This is the Spree fork of state_machine that includes some fixes required for Rails 4.2. We hope to go back to a canonical release if https://github.com/pluginaweek/state_machine/issues/310 decides on a canonical fork.**
4
+
5
+ *state_machine* adds support for creating state machines for attributes on any
6
+ Ruby class.
7
+
8
+ ## Resources
9
+
10
+ API
11
+
12
+ * http://rdoc.info/github/pluginaweek/state_machine/master/frames
13
+
14
+ Bugs
15
+
16
+ * http://github.com/pluginaweek/state_machine/issues
17
+
18
+ Development
19
+
20
+ * http://github.com/pluginaweek/state_machine
21
+
22
+ Testing
23
+
24
+ * http://travis-ci.org/pluginaweek/state_machine
25
+
26
+ Source
27
+
28
+ * git://github.com/pluginaweek/state_machine.git
29
+
30
+ Mailing List
31
+
32
+ * http://groups.google.com/group/pluginaweek-talk
33
+
34
+ ## Description
35
+
36
+ State machines make it dead-simple to manage the behavior of a class. Too often,
37
+ the state of an object is kept by creating multiple boolean attributes and
38
+ deciding how to behave based on the values. This can become cumbersome and
39
+ difficult to maintain when the complexity of your class starts to increase.
40
+
41
+ *state_machine* simplifies this design by introducing the various parts of a real
42
+ state machine, including states, events, transitions, and callbacks. However,
43
+ the api is designed to be so simple you don't even need to know what a
44
+ state machine is :)
45
+
46
+ Some brief, high-level features include:
47
+
48
+ * Defining state machines on any Ruby class
49
+ * Multiple state machines on a single class
50
+ * Namespaced state machines
51
+ * before/after/around/failure transition hooks with explicit transition requirements
52
+ * Integration with ActiveModel, ActiveRecord, DataMapper, Mongoid, MongoMapper, and Sequel
53
+ * State predicates
54
+ * State-driven instance / class behavior
55
+ * State values of any data type
56
+ * Dynamically-generated state values
57
+ * Event parallelization
58
+ * Attribute-based event transitions
59
+ * Path analysis
60
+ * Inheritance
61
+ * Internationalization
62
+ * GraphViz visualization creator
63
+ * YARD integration (Ruby 1.9+ only)
64
+ * Flexible machine syntax
65
+
66
+ Examples of the usage patterns for some of the above features are shown below.
67
+ You can find much more detailed documentation in the actual API.
68
+
69
+ ## Usage
70
+
71
+ ### Example
72
+
73
+ Below is an example of many of the features offered by this plugin, including:
74
+
75
+ * Initial states
76
+ * Namespaced states
77
+ * Transition callbacks
78
+ * Conditional transitions
79
+ * State-driven instance behavior
80
+ * Customized state values
81
+ * Parallel events
82
+ * Path analysis
83
+
84
+ Class definition:
85
+
86
+ ```ruby
87
+ class Vehicle
88
+ attr_accessor :seatbelt_on, :time_used, :auto_shop_busy
89
+
90
+ state_machine :state, :initial => :parked do
91
+ before_transition :parked => any - :parked, :do => :put_on_seatbelt
92
+
93
+ after_transition :on => :crash, :do => :tow
94
+ after_transition :on => :repair, :do => :fix
95
+ after_transition any => :parked do |vehicle, transition|
96
+ vehicle.seatbelt_on = false
97
+ end
98
+
99
+ after_failure :on => :ignite, :do => :log_start_failure
100
+
101
+ around_transition do |vehicle, transition, block|
102
+ start = Time.now
103
+ block.call
104
+ vehicle.time_used += Time.now - start
105
+ end
106
+
107
+ event :park do
108
+ transition [:idling, :first_gear] => :parked
109
+ end
110
+
111
+ event :ignite do
112
+ transition :stalled => same, :parked => :idling
113
+ end
114
+
115
+ event :idle do
116
+ transition :first_gear => :idling
117
+ end
118
+
119
+ event :shift_up do
120
+ transition :idling => :first_gear, :first_gear => :second_gear, :second_gear => :third_gear
121
+ end
122
+
123
+ event :shift_down do
124
+ transition :third_gear => :second_gear, :second_gear => :first_gear
125
+ end
126
+
127
+ event :crash do
128
+ transition all - [:parked, :stalled] => :stalled, :if => lambda {|vehicle| !vehicle.passed_inspection?}
129
+ end
130
+
131
+ event :repair do
132
+ # The first transition that matches the state and passes its conditions
133
+ # will be used
134
+ transition :stalled => :parked, :unless => :auto_shop_busy
135
+ transition :stalled => same
136
+ end
137
+
138
+ state :parked do
139
+ def speed
140
+ 0
141
+ end
142
+ end
143
+
144
+ state :idling, :first_gear do
145
+ def speed
146
+ 10
147
+ end
148
+ end
149
+
150
+ state all - [:parked, :stalled, :idling] do
151
+ def moving?
152
+ true
153
+ end
154
+ end
155
+
156
+ state :parked, :stalled, :idling do
157
+ def moving?
158
+ false
159
+ end
160
+ end
161
+ end
162
+
163
+ state_machine :alarm_state, :initial => :active, :namespace => 'alarm' do
164
+ event :enable do
165
+ transition all => :active
166
+ end
167
+
168
+ event :disable do
169
+ transition all => :off
170
+ end
171
+
172
+ state :active, :value => 1
173
+ state :off, :value => 0
174
+ end
175
+
176
+ def initialize
177
+ @seatbelt_on = false
178
+ @time_used = 0
179
+ @auto_shop_busy = true
180
+ super() # NOTE: This *must* be called, otherwise states won't get initialized
181
+ end
182
+
183
+ def put_on_seatbelt
184
+ @seatbelt_on = true
185
+ end
186
+
187
+ def passed_inspection?
188
+ false
189
+ end
190
+
191
+ def tow
192
+ # tow the vehicle
193
+ end
194
+
195
+ def fix
196
+ # get the vehicle fixed by a mechanic
197
+ end
198
+
199
+ def log_start_failure
200
+ # log a failed attempt to start the vehicle
201
+ end
202
+ end
203
+ ```
204
+
205
+ **Note** the comment made on the `initialize` method in the class. In order for
206
+ state machine attributes to be properly initialized, `super()` must be called.
207
+ See `StateMachine::MacroMethods` for more information about this.
208
+
209
+ Using the above class as an example, you can interact with the state machine
210
+ like so:
211
+
212
+ ```ruby
213
+ vehicle = Vehicle.new # => #<Vehicle:0xb7cf4eac @state="parked", @seatbelt_on=false>
214
+ vehicle.state # => "parked"
215
+ vehicle.state_name # => :parked
216
+ vehicle.human_state_name # => "parked"
217
+ vehicle.parked? # => true
218
+ vehicle.can_ignite? # => true
219
+ vehicle.ignite_transition # => #<StateMachine::Transition attribute=:state event=:ignite from="parked" from_name=:parked to="idling" to_name=:idling>
220
+ vehicle.state_events # => [:ignite]
221
+ vehicle.state_transitions # => [#<StateMachine::Transition attribute=:state event=:ignite from="parked" from_name=:parked to="idling" to_name=:idling>]
222
+ vehicle.speed # => 0
223
+ vehicle.moving? # => false
224
+
225
+ vehicle.ignite # => true
226
+ vehicle.parked? # => false
227
+ vehicle.idling? # => true
228
+ vehicle.speed # => 10
229
+ vehicle # => #<Vehicle:0xb7cf4eac @state="idling", @seatbelt_on=true>
230
+
231
+ vehicle.shift_up # => true
232
+ vehicle.speed # => 10
233
+ vehicle.moving? # => true
234
+ vehicle # => #<Vehicle:0xb7cf4eac @state="first_gear", @seatbelt_on=true>
235
+
236
+ # A generic event helper is available to fire without going through the event's instance method
237
+ vehicle.fire_state_event(:shift_up) # => true
238
+
239
+ # Call state-driven behavior that's undefined for the state raises a NoMethodError
240
+ vehicle.speed # => NoMethodError: super: no superclass method `speed' for #<Vehicle:0xb7cf4eac>
241
+ vehicle # => #<Vehicle:0xb7cf4eac @state="second_gear", @seatbelt_on=true>
242
+
243
+ # The bang (!) operator can raise exceptions if the event fails
244
+ vehicle.park! # => StateMachine::InvalidTransition: Cannot transition state via :park from :second_gear
245
+
246
+ # Generic state predicates can raise exceptions if the value does not exist
247
+ vehicle.state?(:parked) # => false
248
+ vehicle.state?(:invalid) # => IndexError: :invalid is an invalid name
249
+
250
+ # Namespaced machines have uniquely-generated methods
251
+ vehicle.alarm_state # => 1
252
+ vehicle.alarm_state_name # => :active
253
+
254
+ vehicle.can_disable_alarm? # => true
255
+ vehicle.disable_alarm # => true
256
+ vehicle.alarm_state # => 0
257
+ vehicle.alarm_state_name # => :off
258
+ vehicle.can_enable_alarm? # => true
259
+
260
+ vehicle.alarm_off? # => true
261
+ vehicle.alarm_active? # => false
262
+
263
+ # Events can be fired in parallel
264
+ vehicle.fire_events(:shift_down, :enable_alarm) # => true
265
+ vehicle.state_name # => :first_gear
266
+ vehicle.alarm_state_name # => :active
267
+
268
+ vehicle.fire_events!(:ignite, :enable_alarm) # => StateMachine::InvalidTransition: Cannot run events in parallel: ignite, enable_alarm
269
+
270
+ # Human-friendly names can be accessed for states/events
271
+ Vehicle.human_state_name(:first_gear) # => "first gear"
272
+ Vehicle.human_alarm_state_name(:active) # => "active"
273
+
274
+ Vehicle.human_state_event_name(:shift_down) # => "shift down"
275
+ Vehicle.human_alarm_state_event_name(:enable) # => "enable"
276
+
277
+ # States / events can also be references by the string version of their name
278
+ Vehicle.human_state_name('first_gear') # => "first gear"
279
+ Vehicle.human_state_event_name('shift_down') # => "shift down"
280
+
281
+ # Available transition paths can be analyzed for an object
282
+ vehicle.state_paths # => [[#<StateMachine::Transition ...], [#<StateMachine::Transition ...], ...]
283
+ vehicle.state_paths.to_states # => [:parked, :idling, :first_gear, :stalled, :second_gear, :third_gear]
284
+ vehicle.state_paths.events # => [:park, :ignite, :shift_up, :idle, :crash, :repair, :shift_down]
285
+
286
+ # Find all paths that start and end on certain states
287
+ vehicle.state_paths(:from => :parked, :to => :first_gear) # => [[
288
+ # #<StateMachine::Transition attribute=:state event=:ignite from="parked" ...>,
289
+ # #<StateMachine::Transition attribute=:state event=:shift_up from="idling" ...>
290
+ # ]]
291
+ # Skipping state_machine and writing to attributes directly
292
+ vehicle.state = "parked"
293
+ vehicle.state # => "parked"
294
+ vehicle.state_name # => :parked
295
+
296
+ # *Note* that the following is not supported (see StateMachine::MacroMethods#state_machine):
297
+ # vehicle.state = :parked
298
+ ```
299
+
300
+ ## Integrations
301
+
302
+ In addition to being able to define state machines on all Ruby classes, a set of
303
+ out-of-the-box integrations are available for some of the more popular Ruby
304
+ libraries. These integrations add library-specific behavior, allowing for state
305
+ machines to work more tightly with the conventions defined by those libraries.
306
+
307
+ The integrations currently available include:
308
+
309
+ * ActiveModel classes
310
+ * ActiveRecord models
311
+ * DataMapper resources
312
+ * Mongoid models
313
+ * MongoMapper models
314
+ * Sequel models
315
+
316
+ A brief overview of these integrations is described below.
317
+
318
+ ### ActiveModel
319
+
320
+ The ActiveModel integration is useful for both standalone usage and for providing
321
+ the base implementation for ORMs which implement the ActiveModel API. This
322
+ integration adds support for validation errors, dirty attribute tracking, and
323
+ observers. For example,
324
+
325
+ ```ruby
326
+ class Vehicle
327
+ include ActiveModel::Dirty
328
+ include ActiveModel::Validations
329
+ include ActiveModel::Observing
330
+
331
+ attr_accessor :state
332
+ define_attribute_methods [:state]
333
+
334
+ state_machine :initial => :parked do
335
+ before_transition :parked => any - :parked, :do => :put_on_seatbelt
336
+ after_transition any => :parked do |vehicle, transition|
337
+ vehicle.seatbelt = 'off'
338
+ end
339
+ around_transition :benchmark
340
+
341
+ event :ignite do
342
+ transition :parked => :idling
343
+ end
344
+
345
+ state :first_gear, :second_gear do
346
+ validates_presence_of :seatbelt_on
347
+ end
348
+ end
349
+
350
+ def put_on_seatbelt
351
+ ...
352
+ end
353
+
354
+ def benchmark
355
+ ...
356
+ yield
357
+ ...
358
+ end
359
+ end
360
+
361
+ class VehicleObserver < ActiveModel::Observer
362
+ # Callback for :ignite event *before* the transition is performed
363
+ def before_ignite(vehicle, transition)
364
+ # log message
365
+ end
366
+
367
+ # Generic transition callback *after* the transition is performed
368
+ def after_transition(vehicle, transition)
369
+ Audit.log(vehicle, transition)
370
+ end
371
+
372
+ # Generic callback after the transition fails to perform
373
+ def after_failure_to_transition(vehicle, transition)
374
+ Audit.error(vehicle, transition)
375
+ end
376
+ end
377
+ ```
378
+
379
+ For more information about the various behaviors added for ActiveModel state
380
+ machines and how to build new integrations that use ActiveModel, see
381
+ `StateMachine::Integrations::ActiveModel`.
382
+
383
+ ### ActiveRecord
384
+
385
+ The ActiveRecord integration adds support for database transactions, automatically
386
+ saving the record, named scopes, validation errors, and observers. For example,
387
+
388
+ ```ruby
389
+ class Vehicle < ActiveRecord::Base
390
+ state_machine :initial => :parked do
391
+ before_transition :parked => any - :parked, :do => :put_on_seatbelt
392
+ after_transition any => :parked do |vehicle, transition|
393
+ vehicle.seatbelt = 'off'
394
+ end
395
+ around_transition :benchmark
396
+
397
+ event :ignite do
398
+ transition :parked => :idling
399
+ end
400
+
401
+ state :first_gear, :second_gear do
402
+ validates_presence_of :seatbelt_on
403
+ end
404
+ end
405
+
406
+ def put_on_seatbelt
407
+ ...
408
+ end
409
+
410
+ def benchmark
411
+ ...
412
+ yield
413
+ ...
414
+ end
415
+ end
416
+
417
+ class VehicleObserver < ActiveRecord::Observer
418
+ # Callback for :ignite event *before* the transition is performed
419
+ def before_ignite(vehicle, transition)
420
+ # log message
421
+ end
422
+
423
+ # Generic transition callback *after* the transition is performed
424
+ def after_transition(vehicle, transition)
425
+ Audit.log(vehicle, transition)
426
+ end
427
+ end
428
+ ```
429
+
430
+ For more information about the various behaviors added for ActiveRecord state
431
+ machines, see `StateMachine::Integrations::ActiveRecord`.
432
+
433
+ ### DataMapper
434
+
435
+ Like the ActiveRecord integration, the DataMapper integration adds support for
436
+ database transactions, automatically saving the record, named scopes, Extlib-like
437
+ callbacks, validation errors, and observers. For example,
438
+
439
+ ```ruby
440
+ class Vehicle
441
+ include DataMapper::Resource
442
+
443
+ property :id, Serial
444
+ property :state, String
445
+
446
+ state_machine :initial => :parked do
447
+ before_transition :parked => any - :parked, :do => :put_on_seatbelt
448
+ after_transition any => :parked do |transition|
449
+ self.seatbelt = 'off' # self is the record
450
+ end
451
+ around_transition :benchmark
452
+
453
+ event :ignite do
454
+ transition :parked => :idling
455
+ end
456
+
457
+ state :first_gear, :second_gear do
458
+ validates_presence_of :seatbelt_on
459
+ end
460
+ end
461
+
462
+ def put_on_seatbelt
463
+ ...
464
+ end
465
+
466
+ def benchmark
467
+ ...
468
+ yield
469
+ ...
470
+ end
471
+ end
472
+
473
+ class VehicleObserver
474
+ include DataMapper::Observer
475
+
476
+ observe Vehicle
477
+
478
+ # Callback for :ignite event *before* the transition is performed
479
+ before_transition :on => :ignite do |transition|
480
+ # log message (self is the record)
481
+ end
482
+
483
+ # Generic transition callback *after* the transition is performed
484
+ after_transition do |transition|
485
+ Audit.log(self, transition) # self is the record
486
+ end
487
+
488
+ around_transition do |transition, block|
489
+ # mark start time
490
+ block.call
491
+ # mark stop time
492
+ end
493
+
494
+ # Generic callback after the transition fails to perform
495
+ after_transition_failure do |transition|
496
+ Audit.log(self, transition) # self is the record
497
+ end
498
+ end
499
+ ```
500
+
501
+ **Note** that the DataMapper::Observer integration is optional and only available
502
+ when the dm-observer library is installed.
503
+
504
+ For more information about the various behaviors added for DataMapper state
505
+ machines, see `StateMachine::Integrations::DataMapper`.
506
+
507
+ ### Mongoid
508
+
509
+ The Mongoid integration adds support for automatically saving the record,
510
+ basic scopes, validation errors, and observers. For example,
511
+
512
+ ```ruby
513
+ class Vehicle
514
+ include Mongoid::Document
515
+
516
+ state_machine :initial => :parked do
517
+ before_transition :parked => any - :parked, :do => :put_on_seatbelt
518
+ after_transition any => :parked do |vehicle, transition|
519
+ vehicle.seatbelt = 'off' # self is the record
520
+ end
521
+ around_transition :benchmark
522
+
523
+ event :ignite do
524
+ transition :parked => :idling
525
+ end
526
+
527
+ state :first_gear, :second_gear do
528
+ validates_presence_of :seatbelt_on
529
+ end
530
+ end
531
+
532
+ def put_on_seatbelt
533
+ ...
534
+ end
535
+
536
+ def benchmark
537
+ ...
538
+ yield
539
+ ...
540
+ end
541
+ end
542
+
543
+ class VehicleObserver < Mongoid::Observer
544
+ # Callback for :ignite event *before* the transition is performed
545
+ def before_ignite(vehicle, transition)
546
+ # log message
547
+ end
548
+
549
+ # Generic transition callback *after* the transition is performed
550
+ def after_transition(vehicle, transition)
551
+ Audit.log(vehicle, transition)
552
+ end
553
+ end
554
+ ```
555
+
556
+ For more information about the various behaviors added for Mongoid state
557
+ machines, see `StateMachine::Integrations::Mongoid`.
558
+
559
+ ### MongoMapper
560
+
561
+ The MongoMapper integration adds support for automatically saving the record,
562
+ basic scopes, validation errors and callbacks. For example,
563
+
564
+ ```ruby
565
+ class Vehicle
566
+ include MongoMapper::Document
567
+
568
+ state_machine :initial => :parked do
569
+ before_transition :parked => any - :parked, :do => :put_on_seatbelt
570
+ after_transition any => :parked do |vehicle, transition|
571
+ vehicle.seatbelt = 'off' # self is the record
572
+ end
573
+ around_transition :benchmark
574
+
575
+ event :ignite do
576
+ transition :parked => :idling
577
+ end
578
+
579
+ state :first_gear, :second_gear do
580
+ validates_presence_of :seatbelt_on
581
+ end
582
+ end
583
+
584
+ def put_on_seatbelt
585
+ ...
586
+ end
587
+
588
+ def benchmark
589
+ ...
590
+ yield
591
+ ...
592
+ end
593
+ end
594
+ ```
595
+
596
+ For more information about the various behaviors added for MongoMapper state
597
+ machines, see `StateMachine::Integrations::MongoMapper`.
598
+
599
+ ### Sequel
600
+
601
+ Like the ActiveRecord integration, the Sequel integration adds support for
602
+ database transactions, automatically saving the record, named scopes, validation
603
+ errors and callbacks. For example,
604
+
605
+ ```ruby
606
+ class Vehicle < Sequel::Model
607
+ plugin :validation_class_methods
608
+
609
+ state_machine :initial => :parked do
610
+ before_transition :parked => any - :parked, :do => :put_on_seatbelt
611
+ after_transition any => :parked do |transition|
612
+ self.seatbelt = 'off' # self is the record
613
+ end
614
+ around_transition :benchmark
615
+
616
+ event :ignite do
617
+ transition :parked => :idling
618
+ end
619
+
620
+ state :first_gear, :second_gear do
621
+ validates_presence_of :seatbelt_on
622
+ end
623
+ end
624
+
625
+ def put_on_seatbelt
626
+ ...
627
+ end
628
+
629
+ def benchmark
630
+ ...
631
+ yield
632
+ ...
633
+ end
634
+ end
635
+ ```
636
+
637
+ For more information about the various behaviors added for Sequel state
638
+ machines, see `StateMachine::Integrations::Sequel`.
639
+
640
+ ## Additional Topics
641
+
642
+ ### Explicit vs. Implicit Event Transitions
643
+
644
+ Every event defined for a state machine generates an instance method on the
645
+ class that allows the event to be explicitly triggered. Most of the examples in
646
+ the state_machine documentation use this technique. However, with some types of
647
+ integrations, like ActiveRecord, you can also *implicitly* fire events by
648
+ setting a special attribute on the instance.
649
+
650
+ Suppose you're using the ActiveRecord integration and the following model is
651
+ defined:
652
+
653
+ ```ruby
654
+ class Vehicle < ActiveRecord::Base
655
+ state_machine :initial => :parked do
656
+ event :ignite do
657
+ transition :parked => :idling
658
+ end
659
+ end
660
+ end
661
+ ```
662
+
663
+ To trigger the `ignite` event, you would typically call the `Vehicle#ignite`
664
+ method like so:
665
+
666
+ ```ruby
667
+ vehicle = Vehicle.create # => #<Vehicle id=1 state="parked">
668
+ vehicle.ignite # => true
669
+ vehicle.state # => "idling"
670
+ ```
671
+
672
+ This is referred to as an *explicit* event transition. The same behavior can
673
+ also be achieved *implicitly* by setting the state event attribute and invoking
674
+ the action associated with the state machine. For example:
675
+
676
+ ```ruby
677
+ vehicle = Vehicle.create # => #<Vehicle id=1 state="parked">
678
+ vehicle.state_event = "ignite" # => "ignite"
679
+ vehicle.save # => true
680
+ vehicle.state # => "idling"
681
+ vehicle.state_event # => nil
682
+ ```
683
+
684
+ As you can see, the `ignite` event was automatically triggered when the `save`
685
+ action was called. This is particularly useful if you want to allow users to
686
+ drive the state transitions from a web API.
687
+
688
+ See each integration's API documentation for more information on the implicit
689
+ approach.
690
+
691
+ ### Symbols vs. Strings
692
+
693
+ In all of the examples used throughout the documentation, you'll notice that
694
+ states and events are almost always referenced as symbols. This isn't a
695
+ requirement, but rather a suggested best practice.
696
+
697
+ You can very well define your state machine with Strings like so:
698
+
699
+ ```ruby
700
+ class Vehicle
701
+ state_machine :initial => 'parked' do
702
+ event 'ignite' do
703
+ transition 'parked' => 'idling'
704
+ end
705
+
706
+ # ...
707
+ end
708
+ end
709
+ ```
710
+
711
+ You could even use numbers as your state / event names. The **important** thing
712
+ to keep in mind is that the type being used for referencing states / events in
713
+ your machine definition must be **consistent**. If you're using Symbols, then
714
+ all states / events must use Symbols. Otherwise you'll encounter the following
715
+ error:
716
+
717
+ ```ruby
718
+ class Vehicle
719
+ state_machine do
720
+ event :ignite do
721
+ transition :parked => 'idling'
722
+ end
723
+ end
724
+ end
725
+
726
+ # => ArgumentError: "idling" state defined as String, :parked defined as Symbol; all states must be consistent
727
+ ```
728
+
729
+ There **is** an exception to this rule. The consistency is only required within
730
+ the definition itself. However, when the machine's helper methods are called
731
+ with input from external sources, such as a web form, state_machine will map
732
+ that input to a String / Symbol. For example:
733
+
734
+ ```ruby
735
+ class Vehicle
736
+ state_machine :initial => :parked do
737
+ event :ignite do
738
+ transition :parked => :idling
739
+ end
740
+ end
741
+ end
742
+
743
+ v = Vehicle.new # => #<Vehicle:0xb71da5f8 @state="parked">
744
+ v.state?('parked') # => true
745
+ v.state?(:parked) # => true
746
+ ```
747
+
748
+ **Note** that none of this actually has to do with the type of the value that
749
+ gets stored. By default, all state values are assumed to be string -- regardless
750
+ of whether the state names are symbols or strings. If you want to store states
751
+ as symbols instead you'll have to be explicit about it:
752
+
753
+ ```ruby
754
+ class Vehicle
755
+ state_machine :initial => :parked do
756
+ event :ignite do
757
+ transition :parked => :idling
758
+ end
759
+
760
+ states.each do |state|
761
+ self.state(state.name, :value => state.name.to_sym)
762
+ end
763
+ end
764
+ end
765
+
766
+ v = Vehicle.new # => #<Vehicle:0xb71da5f8 @state=:parked>
767
+ v.state?('parked') # => true
768
+ v.state?(:parked) # => true
769
+ ```
770
+
771
+ ### Syntax flexibility
772
+
773
+ Although state_machine introduces a simplified syntax, it still remains
774
+ backwards compatible with previous versions and other state-related libraries by
775
+ providing some flexibility around how transitions are defined. See below for an
776
+ overview of these syntaxes.
777
+
778
+ #### Verbose syntax
779
+
780
+ In general, it's recommended that state machines use the implicit syntax for
781
+ transitions. However, you can be a little more explicit and verbose about
782
+ transitions by using the `:from`, `:except_from`, `:to`,
783
+ and `:except_to` options.
784
+
785
+ For example, transitions and callbacks can be defined like so:
786
+
787
+ ```ruby
788
+ class Vehicle
789
+ state_machine :initial => :parked do
790
+ before_transition :from => :parked, :except_to => :parked, :do => :put_on_seatbelt
791
+ after_transition :to => :parked do |transition|
792
+ self.seatbelt = 'off' # self is the record
793
+ end
794
+
795
+ event :ignite do
796
+ transition :from => :parked, :to => :idling
797
+ end
798
+ end
799
+ end
800
+ ```
801
+
802
+ #### Transition context
803
+
804
+ Some flexibility is provided around the context in which transitions can be
805
+ defined. In almost all examples throughout the documentation, transitions are
806
+ defined within the context of an event. If you prefer to have state machines
807
+ defined in the context of a **state** either out of preference or in order to
808
+ easily migrate from a different library, you can do so as shown below:
809
+
810
+ ```ruby
811
+ class Vehicle
812
+ state_machine :initial => :parked do
813
+ ...
814
+
815
+ state :parked do
816
+ transition :to => :idling, :on => [:ignite, :shift_up], :if => :seatbelt_on?
817
+
818
+ def speed
819
+ 0
820
+ end
821
+ end
822
+
823
+ state :first_gear do
824
+ transition :to => :second_gear, :on => :shift_up
825
+
826
+ def speed
827
+ 10
828
+ end
829
+ end
830
+
831
+ state :idling, :first_gear do
832
+ transition :to => :parked, :on => :park
833
+ end
834
+ end
835
+ end
836
+ ```
837
+
838
+ In the above example, there's no need to specify the `from` state for each
839
+ transition since it's inferred from the context.
840
+
841
+ You can also define transitions completely outside the context of a particular
842
+ state / event. This may be useful in cases where you're building a state
843
+ machine from a data store instead of part of the class definition. See the
844
+ example below:
845
+
846
+ ```ruby
847
+ class Vehicle
848
+ state_machine :initial => :parked do
849
+ ...
850
+
851
+ transition :parked => :idling, :on => [:ignite, :shift_up]
852
+ transition :first_gear => :second_gear, :second_gear => :third_gear, :on => :shift_up
853
+ transition [:idling, :first_gear] => :parked, :on => :park
854
+ transition [:idling, :first_gear] => :parked, :on => :park
855
+ transition all - [:parked, :stalled] => :stalled, :unless => :auto_shop_busy?
856
+ end
857
+ end
858
+ ```
859
+
860
+ Notice that in these alternative syntaxes:
861
+
862
+ * You can continue to configure `:if` and `:unless` conditions
863
+ * You can continue to define `from` states (when in the machine context) using
864
+ the `all`, `any`, and `same` helper methods
865
+
866
+ ### Static / Dynamic definitions
867
+
868
+ In most cases, the definition of a state machine is **static**. That is to say,
869
+ the states, events and possible transitions are known ahead of time even though
870
+ they may depend on data that's only known at runtime. For example, certain
871
+ transitions may only be available depending on an attribute on that object it's
872
+ being run on. All of the documentation in this library define static machines
873
+ like so:
874
+
875
+ ```ruby
876
+ class Vehicle
877
+ state_machine :state, :initial => :parked do
878
+ event :park do
879
+ transition [:idling, :first_gear] => :parked
880
+ end
881
+
882
+ ...
883
+ end
884
+ end
885
+ ```
886
+
887
+ However, there may be cases where the definition of a state machine is **dynamic**.
888
+ This means that you don't know the possible states or events for a machine until
889
+ runtime. For example, you may allow users in your application to manage the
890
+ state machine of a project or task in your system. This means that the list of
891
+ transitions (and their associated states / events) could be stored externally,
892
+ such as in a database. In a case like this, you can define dynamically-generated
893
+ state machines like so:
894
+
895
+ ```ruby
896
+ class Vehicle
897
+ attr_accessor :state
898
+
899
+ # Make sure the machine gets initialized so the initial state gets set properly
900
+ def initialize(*)
901
+ super
902
+ machine
903
+ end
904
+
905
+ # Replace this with an external source (like a db)
906
+ def transitions
907
+ [
908
+ {:parked => :idling, :on => :ignite},
909
+ {:idling => :first_gear, :first_gear => :second_gear, :on => :shift_up}
910
+ # ...
911
+ ]
912
+ end
913
+
914
+ # Create a state machine for this vehicle instance dynamically based on the
915
+ # transitions defined from the source above
916
+ def machine
917
+ vehicle = self
918
+ @machine ||= Machine.new(vehicle, :initial => :parked, :action => :save) do
919
+ vehicle.transitions.each {|attrs| transition(attrs)}
920
+ end
921
+ end
922
+
923
+ def save
924
+ # Save the state change...
925
+ true
926
+ end
927
+ end
928
+
929
+ # Generic class for building machines
930
+ class Machine
931
+ def self.new(object, *args, &block)
932
+ machine_class = Class.new
933
+ machine = machine_class.state_machine(*args, &block)
934
+ attribute = machine.attribute
935
+ action = machine.action
936
+
937
+ # Delegate attributes
938
+ machine_class.class_eval do
939
+ define_method(:definition) { machine }
940
+ define_method(attribute) { object.send(attribute) }
941
+ define_method("#{attribute}=") {|value| object.send("#{attribute}=", value) }
942
+ define_method(action) { object.send(action) } if action
943
+ end
944
+
945
+ machine_class.new
946
+ end
947
+ end
948
+
949
+ vehicle = Vehicle.new # => #<Vehicle:0xb708412c @state="parked" ...>
950
+ vehicle.state # => "parked"
951
+ vehicle.machine.ignite # => true
952
+ vehicle.machine.state # => "idling
953
+ vehicle.state # => "idling"
954
+ vehicle.machine.state_transitions # => [#<StateMachine::Transition ...>]
955
+ vehicle.machine.definition.states.keys # => :first_gear, :second_gear, :parked, :idling
956
+ ```
957
+
958
+ As you can see, state_machine provides enough flexibility for you to be able
959
+ to create new machine definitions on the fly based on an external source of
960
+ transitions.
961
+
962
+ ### Core Extensions
963
+
964
+ By default, state_machine extends the Ruby core with a `state_machine` method on
965
+ `Class`. All other parts of the library are confined within the `StateMachine`
966
+ namespace. While this isn't wholly necessary, it also doesn't have any performance
967
+ impact and makes it truly feel like an extension to the language. This is very
968
+ similar to the way that you'll find `yaml`, `json`, or other libraries adding a
969
+ simple method to all objects just by loading the library.
970
+
971
+ However, if you'd like to avoid having state_machine add this extension to the
972
+ Ruby core, you can do so like so:
973
+
974
+ ```ruby
975
+ require 'state_machine/core'
976
+
977
+ class Vehicle
978
+ extend StateMachine::MacroMethods
979
+
980
+ state_machine do
981
+ # ...
982
+ end
983
+ end
984
+ ```
985
+
986
+ If you're using a gem loader like Bundler, you can explicitly indicate which
987
+ file to load:
988
+
989
+ ```ruby
990
+ # In Gemfile
991
+ ...
992
+ gem 'state_machine', :require => 'state_machine/core'
993
+ ```
994
+
995
+ ## Tools
996
+
997
+ ### Generating graphs
998
+
999
+ This library comes with built-in support for generating di-graphs based on the
1000
+ events, states, and transitions defined for a state machine using [GraphViz](http://www.graphviz.org).
1001
+ This requires that both the `ruby-graphviz` gem and graphviz library be
1002
+ installed on the system.
1003
+
1004
+ #### Examples
1005
+
1006
+ To generate a graph for a specific file / class:
1007
+
1008
+ ```bash
1009
+ rake state_machine:draw FILE=vehicle.rb CLASS=Vehicle
1010
+ ```
1011
+
1012
+ To save files to a specific path:
1013
+
1014
+ ```bash
1015
+ rake state_machine:draw FILE=vehicle.rb CLASS=Vehicle TARGET=files
1016
+ ```
1017
+
1018
+ To customize the image format / orientation:
1019
+
1020
+ ```bash
1021
+ rake state_machine:draw FILE=vehicle.rb CLASS=Vehicle FORMAT=jpg ORIENTATION=landscape
1022
+ ```
1023
+
1024
+ See http://rdoc.info/github/glejeune/Ruby-Graphviz/Constants for the list of
1025
+ supported image formats. If resolution is an issue, the svg format may offer
1026
+ better results.
1027
+
1028
+ To generate multiple state machine graphs:
1029
+
1030
+ ```bash
1031
+ rake state_machine:draw FILE=vehicle.rb,car.rb CLASS=Vehicle,Car
1032
+ ```
1033
+
1034
+ To use human state / event names:
1035
+
1036
+ ```bash
1037
+ rake state_machine:draw FILE=vehicle.rb CLASS=Vehicle HUMAN_NAMES=true
1038
+ ```
1039
+
1040
+ **Note** that this will generate a different file for every state machine defined
1041
+ in the class. The generated files will use an output filename of the format
1042
+ `#{class_name}_#{machine_name}.#{format}`.
1043
+
1044
+ For examples of actual images generated using this task, see those under the
1045
+ examples folder.
1046
+
1047
+ ### Interactive graphs
1048
+
1049
+ Jean Bovet's [Visual Automata Simulator](http://www.cs.usfca.edu/~jbovet/vas.html)
1050
+ is a great tool for "simulating, visualizing and transforming finite state
1051
+ automata and Turing Machines". It can help in the creation of states and events
1052
+ for your models. It is cross-platform, written in Java.
1053
+
1054
+ ### Generating documentation
1055
+
1056
+ If you use YARD to generate documentation for your projects, state_machine can
1057
+ be enabled to generate API docs for auto-generated methods from each state machine
1058
+ definition as well as providing embedded visualizations.
1059
+
1060
+ See the generated API documentation under the examples folder to see what the
1061
+ output looks like.
1062
+
1063
+ To enable the YARD integration, you'll need to add state_machine to the list of
1064
+ YARD's plugins by editing the global YARD config:
1065
+
1066
+ ~/.yard/config:
1067
+
1068
+ ```yaml
1069
+ load_plugins: true
1070
+ autoload_plugins:
1071
+ - state_machine
1072
+ ```
1073
+
1074
+ Once enabled, simply generate your documentation like you normally do.
1075
+
1076
+ *Note* that this only works for Ruby 1.9+.
1077
+
1078
+ ## Web Frameworks
1079
+
1080
+ ### Ruby on Rails
1081
+
1082
+ Integrating state_machine into your Ruby on Rails application is straightforward
1083
+ and provides a few additional features specific to the framework. To get
1084
+ started, following the steps below.
1085
+
1086
+ #### 1. Install the gem
1087
+
1088
+ If using Rails 2.x:
1089
+
1090
+ ```ruby
1091
+ # In config/environment.rb
1092
+ ...
1093
+ Rails::Initializer.run do |config|
1094
+ ...
1095
+ config.gem 'state_machine', :version => '~> 1.0'
1096
+ ...
1097
+ end
1098
+ ```
1099
+
1100
+ If using Rails 3.x or up:
1101
+
1102
+ ```ruby
1103
+ # In Gemfile
1104
+ ...
1105
+ gem 'state_machine'
1106
+ gem 'ruby-graphviz', :require => 'graphviz' # Optional: only required for graphing
1107
+ ```
1108
+
1109
+ As usual, run `bundle install` to load the gems.
1110
+
1111
+ #### 2. Create a model
1112
+
1113
+ Create a model with a field to store the state, along with other any other
1114
+ fields your application requires:
1115
+
1116
+ ```bash
1117
+ $ rails generate model Vehicle state:string
1118
+ $ rake db:migrate
1119
+ ```
1120
+
1121
+ #### 3. Configure the state machine
1122
+
1123
+ Add the state machine to your model. Following the examples above,
1124
+ *app/models/vehicle.rb* might become:
1125
+
1126
+ ```ruby
1127
+ class Vehicle < ActiveRecord::Base
1128
+ state_machine :initial => :parked do
1129
+ before_transition :parked => any - :parked, :do => :put_on_seatbelt
1130
+ ...
1131
+ end
1132
+ end
1133
+ ```
1134
+
1135
+ #### Rake tasks
1136
+
1137
+ There is a special integration Rake task for generating state machines for
1138
+ classes used in a Ruby on Rails application. This task will load the application
1139
+ environment, meaning that it's unnecessary to specify the actual file to load.
1140
+
1141
+ For example,
1142
+
1143
+ ```bash
1144
+ rake state_machine:draw CLASS=Vehicle
1145
+ ```
1146
+
1147
+ If you are using this library as a gem in Rails 2.x, the following must be added
1148
+ to the end of your application's Rakefile in order for the above task to work:
1149
+
1150
+ ```ruby
1151
+ require 'tasks/state_machine'
1152
+ ```
1153
+
1154
+ ### Merb
1155
+
1156
+ #### Rake tasks
1157
+
1158
+ Like Ruby on Rails, there is a special integration Rake task for generating
1159
+ state machines for classes used in a Merb application. This task will load the
1160
+ application environment, meaning that it's unnecessary to specify the actual
1161
+ files to load.
1162
+
1163
+ For example,
1164
+
1165
+ ```bash
1166
+ rake state_machine:draw CLASS=Vehicle
1167
+ ```
1168
+
1169
+ ## Testing
1170
+
1171
+ To run the core test suite (does **not** test any of the integrations):
1172
+
1173
+ ```bash
1174
+ bundle install
1175
+ bundle exec rake test
1176
+ ```
1177
+
1178
+ To run integration tests:
1179
+
1180
+ ```bash
1181
+ bundle install
1182
+ rake appraisal:install
1183
+ rake appraisal:test
1184
+ ```
1185
+
1186
+ You can also test a specific version:
1187
+
1188
+ ```bash
1189
+ rake appraisal:active_model-3.0.0 test
1190
+ rake appraisal:active_record-2.0.0 test
1191
+ rake appraisal:data_mapper-0.9.4 test
1192
+ rake appraisal:mongoid-2.0.0 test
1193
+ rake appraisal:mongo_mapper-0.5.5 test
1194
+ rake appraisal:sequel-2.8.0 test
1195
+ ```
1196
+
1197
+ ## Caveats
1198
+
1199
+ The following caveats should be noted when using state_machine:
1200
+
1201
+ * Overridden event methods won't get invoked when using attribute-based event transitions
1202
+ * **DataMapper**: Attribute-based event transitions are disabled when using dm-validations 0.9.4 - 0.9.6
1203
+ * **DataMapper**: Transitions cannot persist states when run from after :create / :save callbacks
1204
+ * **JRuby / Rubinius**: around_transition callbacks in ORM integrations won't work on JRuby since it doesn't support continuations
1205
+ * **Factory Girl**: Dynamic initial states don't work because of the way factory_girl
1206
+ builds objects. You can work around this in a few ways:
1207
+ 1. Use a default state that is common across all objects and rely on events to
1208
+ determine the actual initial state for your object.
1209
+ 2. Assuming you're not using state-driven behavior on initialization, you can
1210
+ re-initialize states after the fact:
1211
+
1212
+ ```ruby
1213
+ # Re-initialize in FactoryGirl
1214
+ FactoryGirl.define do
1215
+ factory :vehicle do
1216
+ after_build {|user| user.send(:initialize_state_machines, :dynamic => :force)}
1217
+ end
1218
+ end
1219
+
1220
+ # Alternatively re-initialize in your model
1221
+ class Vehicle < ActiveRecord::Base
1222
+ ...
1223
+ before_validation :on => :create {|user| user.send(:initialize_state_machines, :dynamic => :force)}
1224
+ end
1225
+ ```
1226
+
1227
+ ## Dependencies
1228
+
1229
+ Ruby versions officially supported and tested:
1230
+
1231
+ * Ruby (MRI) 1.8.6+
1232
+ * JRuby (1.8, 1.9)
1233
+ * Rubinius (1.8, 1.9)
1234
+
1235
+ ORM versions officially supported and tested:
1236
+
1237
+ * [ActiveModel](http://rubyonrails.org) integration: 3.0.0 or later
1238
+ * [ActiveRecord](http://rubyonrails.org) integration: 2.0.0 or later
1239
+ * [DataMapper](http://datamapper.org) integration: 0.9.4 or later
1240
+ * [Mongoid](http://mongoid.org) integration: 2.0.0 or later
1241
+ * [MongoMapper](http://mongomapper.com) integration: 0.5.5 or later
1242
+ * [Sequel](http://sequel.rubyforge.org) integration: 2.8.0 or later
1243
+
1244
+ If graphing state machine:
1245
+
1246
+ * [ruby-graphviz](http://github.com/glejeune/Ruby-Graphviz): 0.9.17 or later