hsume2-state_machine 1.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (110) hide show
  1. data/CHANGELOG.rdoc +413 -0
  2. data/LICENSE +20 -0
  3. data/README.rdoc +717 -0
  4. data/Rakefile +77 -0
  5. data/examples/AutoShop_state.png +0 -0
  6. data/examples/Car_state.png +0 -0
  7. data/examples/TrafficLight_state.png +0 -0
  8. data/examples/Vehicle_state.png +0 -0
  9. data/examples/auto_shop.rb +11 -0
  10. data/examples/car.rb +19 -0
  11. data/examples/merb-rest/controller.rb +51 -0
  12. data/examples/merb-rest/model.rb +28 -0
  13. data/examples/merb-rest/view_edit.html.erb +24 -0
  14. data/examples/merb-rest/view_index.html.erb +23 -0
  15. data/examples/merb-rest/view_new.html.erb +13 -0
  16. data/examples/merb-rest/view_show.html.erb +17 -0
  17. data/examples/rails-rest/controller.rb +43 -0
  18. data/examples/rails-rest/migration.rb +11 -0
  19. data/examples/rails-rest/model.rb +23 -0
  20. data/examples/rails-rest/view_edit.html.erb +25 -0
  21. data/examples/rails-rest/view_index.html.erb +23 -0
  22. data/examples/rails-rest/view_new.html.erb +14 -0
  23. data/examples/rails-rest/view_show.html.erb +17 -0
  24. data/examples/traffic_light.rb +7 -0
  25. data/examples/vehicle.rb +31 -0
  26. data/init.rb +1 -0
  27. data/lib/state_machine.rb +448 -0
  28. data/lib/state_machine/alternate_machine.rb +79 -0
  29. data/lib/state_machine/assertions.rb +36 -0
  30. data/lib/state_machine/branch.rb +224 -0
  31. data/lib/state_machine/callback.rb +236 -0
  32. data/lib/state_machine/condition_proxy.rb +94 -0
  33. data/lib/state_machine/error.rb +13 -0
  34. data/lib/state_machine/eval_helpers.rb +86 -0
  35. data/lib/state_machine/event.rb +304 -0
  36. data/lib/state_machine/event_collection.rb +139 -0
  37. data/lib/state_machine/extensions.rb +149 -0
  38. data/lib/state_machine/initializers.rb +4 -0
  39. data/lib/state_machine/initializers/merb.rb +1 -0
  40. data/lib/state_machine/initializers/rails.rb +25 -0
  41. data/lib/state_machine/integrations.rb +110 -0
  42. data/lib/state_machine/integrations/active_model.rb +502 -0
  43. data/lib/state_machine/integrations/active_model/locale.rb +11 -0
  44. data/lib/state_machine/integrations/active_model/observer.rb +45 -0
  45. data/lib/state_machine/integrations/active_model/versions.rb +31 -0
  46. data/lib/state_machine/integrations/active_record.rb +424 -0
  47. data/lib/state_machine/integrations/active_record/locale.rb +20 -0
  48. data/lib/state_machine/integrations/active_record/versions.rb +143 -0
  49. data/lib/state_machine/integrations/base.rb +91 -0
  50. data/lib/state_machine/integrations/data_mapper.rb +392 -0
  51. data/lib/state_machine/integrations/data_mapper/observer.rb +210 -0
  52. data/lib/state_machine/integrations/data_mapper/versions.rb +62 -0
  53. data/lib/state_machine/integrations/mongo_mapper.rb +272 -0
  54. data/lib/state_machine/integrations/mongo_mapper/locale.rb +4 -0
  55. data/lib/state_machine/integrations/mongo_mapper/versions.rb +110 -0
  56. data/lib/state_machine/integrations/mongoid.rb +357 -0
  57. data/lib/state_machine/integrations/mongoid/locale.rb +4 -0
  58. data/lib/state_machine/integrations/mongoid/versions.rb +18 -0
  59. data/lib/state_machine/integrations/sequel.rb +428 -0
  60. data/lib/state_machine/integrations/sequel/versions.rb +36 -0
  61. data/lib/state_machine/machine.rb +1873 -0
  62. data/lib/state_machine/machine_collection.rb +87 -0
  63. data/lib/state_machine/matcher.rb +123 -0
  64. data/lib/state_machine/matcher_helpers.rb +54 -0
  65. data/lib/state_machine/node_collection.rb +157 -0
  66. data/lib/state_machine/path.rb +120 -0
  67. data/lib/state_machine/path_collection.rb +90 -0
  68. data/lib/state_machine/state.rb +271 -0
  69. data/lib/state_machine/state_collection.rb +112 -0
  70. data/lib/state_machine/transition.rb +458 -0
  71. data/lib/state_machine/transition_collection.rb +244 -0
  72. data/lib/tasks/state_machine.rake +1 -0
  73. data/lib/tasks/state_machine.rb +27 -0
  74. data/test/files/en.yml +17 -0
  75. data/test/files/switch.rb +11 -0
  76. data/test/functional/alternate_state_machine_test.rb +122 -0
  77. data/test/functional/state_machine_test.rb +993 -0
  78. data/test/test_helper.rb +4 -0
  79. data/test/unit/assertions_test.rb +40 -0
  80. data/test/unit/branch_test.rb +890 -0
  81. data/test/unit/callback_test.rb +701 -0
  82. data/test/unit/condition_proxy_test.rb +328 -0
  83. data/test/unit/error_test.rb +43 -0
  84. data/test/unit/eval_helpers_test.rb +222 -0
  85. data/test/unit/event_collection_test.rb +358 -0
  86. data/test/unit/event_test.rb +985 -0
  87. data/test/unit/integrations/active_model_test.rb +1097 -0
  88. data/test/unit/integrations/active_record_test.rb +2021 -0
  89. data/test/unit/integrations/base_test.rb +99 -0
  90. data/test/unit/integrations/data_mapper_test.rb +1909 -0
  91. data/test/unit/integrations/mongo_mapper_test.rb +1611 -0
  92. data/test/unit/integrations/mongoid_test.rb +1591 -0
  93. data/test/unit/integrations/sequel_test.rb +1523 -0
  94. data/test/unit/integrations_test.rb +61 -0
  95. data/test/unit/invalid_event_test.rb +20 -0
  96. data/test/unit/invalid_parallel_transition_test.rb +18 -0
  97. data/test/unit/invalid_transition_test.rb +77 -0
  98. data/test/unit/machine_collection_test.rb +599 -0
  99. data/test/unit/machine_test.rb +3043 -0
  100. data/test/unit/matcher_helpers_test.rb +37 -0
  101. data/test/unit/matcher_test.rb +155 -0
  102. data/test/unit/node_collection_test.rb +217 -0
  103. data/test/unit/path_collection_test.rb +266 -0
  104. data/test/unit/path_test.rb +485 -0
  105. data/test/unit/state_collection_test.rb +310 -0
  106. data/test/unit/state_machine_test.rb +31 -0
  107. data/test/unit/state_test.rb +924 -0
  108. data/test/unit/transition_collection_test.rb +2102 -0
  109. data/test/unit/transition_test.rb +1541 -0
  110. metadata +207 -0
data/CHANGELOG.rdoc ADDED
@@ -0,0 +1,413 @@
1
+ == master
2
+
3
+ == 1.0.1 / 2011-05-30
4
+
5
+ * Add the ability to ignore method conflicts for helpers
6
+ * Generate warnings for any helper, not just state helpers, that has a conflicting method defined in the class
7
+ * Fix scopes in Sequel not working if the table name contains double underscores or is not a string/symbol
8
+ * Add full support for chaining state scopes within Sequel integrations
9
+ * Fix Rails 3.1 deprecation warnings for configuring engine locales [Stefan Penner]
10
+
11
+ == 1.0.0 / 2011-05-12
12
+
13
+ * Celebrate
14
+
15
+ == 0.10.4 / 2011-04-14
16
+
17
+ * Fix translations not being available under certain environments in Rails applications
18
+
19
+ == 0.10.3 / 2011-04-07
20
+
21
+ * Fix state initialization failing in ActiveRecord 3.0.2+ when using with_state scopes for the default scope
22
+
23
+ == 0.10.2 / 2011-03-31
24
+
25
+ * Use more integrated state initialization hooks for ActiveRecord, Mongoid, and Sequel
26
+ * Remove mass-assignment filtering usage in all ORM integrations
27
+ * Only support official Mongoid 2.0.0 release and up (no more RC support)
28
+ * Fix attributes getting initialized more than once if different state machines use the same attribute
29
+ * Only initialize states if state is blank and blank is not a valid state
30
+ * Fix instance / class helpers failing when used with certain libraries (such as Thin)
31
+
32
+ == 0.10.1 / 2011-03-22
33
+
34
+ * Fix classes with multiple state machines failing to initialize in ActiveRecord / Mongoid / Sequel integrations
35
+
36
+ == 0.10.0 / 2011-03-19
37
+
38
+ * Support callback terminators in MongoMapper 0.9.0+
39
+ * Fix pluralization integration on DataMapper 1.0.0 and 1.1.0
40
+ * Allow transition guards to be bypassed for event / transition / path helpers
41
+ * Allow state / condition requirements to be specified for all event / transition / path helpers
42
+ * Add the ability to skip automatically initializing state machines on #initialize
43
+ * Add #{name}_paths for walking the available paths in a state machine
44
+ * Add Mongoid 2.0.0+ support
45
+ * Use around hooks to improve compatibility with other libraries in ActiveModel / ActiveRecord / MongoMapper integrations
46
+ * Add support for MassAssignmentSecurity feature in ActiveModel integrations
47
+ * Add support for more observer hooks within MongoMapper integrations
48
+ * Add i18n support for MongoMapper validation errors
49
+ * Update support for MongoMapper integration based on rails3 branch
50
+ * Fix objects not getting marked as dirty in all integrations when #{name}_event is set
51
+ * Generate warnings when conflicting state / event names are detected
52
+ * Allow fallback to generic state predicates when individual predicates are already defined in the owner class
53
+ * Replace :include_failures after_transition option with new after_failure callback
54
+ * Provide access to transition context when raising InvalidEvent / InvalidTransition exceptions
55
+
56
+ == 0.9.4 / 2010-08-01
57
+
58
+ * Fix validation / save hooks in Sequel 3.14.0+
59
+ * Fix integration with dirty attribute tracking on DataMapper 1.0.1+
60
+ * Fix DataMapper 1.0.1+ tests producing warnings
61
+ * Fix validation error warnings in ActiveModel / ActiveRecord 3.0.0 beta5+
62
+ * Fix mass-assignment sanitization breaking in ActiveRecord 3.0.0 beta5+ [Akira Matsuda]
63
+
64
+ == 0.9.3 / 2010-06-26
65
+
66
+ * Allow access to human state / event names in transitions and for the current state
67
+ * Use human state / event names in error messages
68
+ * Fix event names being used inconsistently in error messages
69
+ * Allow access to the humanized version of state / event names via human_state_name / human_state_event_name
70
+ * Allow MongoMapper 0.8.0+ scopes to be chainable
71
+ * Fix i18n deprecation warnings in ActiveModel / ActiveRecord 3.0.0.beta4
72
+ * Fix default error message translations overriding existing locales in ActiveModel / ActiveRecord
73
+
74
+ == 0.9.2 / 2010-05-24
75
+
76
+ * Fix MongoMapper integration failing in Ruby 1.9.2
77
+ * Fix Rakefile not loading in Ruby 1.9.2 [Andrea Longhi]
78
+ * Fix nil / false :integration configuration not being respected
79
+
80
+ == 0.9.1 / 2010-05-02
81
+
82
+ * Fix ActiveRecord 2.0.0 - 2.2.3 integrations failing if version info isn't already loaded
83
+ * Fix integration with dirty attribute tracking on DataMapper 0.10.3
84
+ * Fix observers failing in ActiveRecord 3.0.0.beta4+ integrations
85
+ * Fix deprecation warning in Rails 3 railtie [Chris Yuan]
86
+
87
+ == 0.9.0 / 2010-04-12
88
+
89
+ * Use attribute-based event transitions whenever possible to ensure consistency
90
+ * Fix action helpers being defined when the action is *only* defined in the machine's owner class
91
+ * Disable attribute-based event transitions in DataMapper 0.9.4 - 0.9.6 when dm-validations is being used
92
+ * Add support for DataMapper 0.10.3+
93
+ * Add around_transition callbacks
94
+ * Fix transition failures during save not being handled correctly in Sequel 2.12.0+
95
+ * Fix attribute-based event transitions not hooking in properly in DataMapper 0.10.0+ and Sequel 2.12.0+
96
+ * Fix dynamic initial states causing errors in Ruby 1.9+ if no arguments are defined in the block
97
+ * Add MongoMapper 0.5.5+ support
98
+ * Add ActiveModel 3.0+ support for use with integrations that implement its interface
99
+ * Fix DataMapper integration failing when ActiveSupport is loaded in place of Extlib
100
+ * Add version dependencies for ruby-graphviz
101
+ * Remove app-specific rails / merb rake tasks in favor of always running state_machine:draw
102
+ * Add Rails 3 railtie for automatically loading rake tasks when installed as a gem
103
+
104
+ == 0.8.1 / 2010-03-14
105
+
106
+ * Release gems via rake-gemcutter instead of rubyforge
107
+ * Move rake tasks to lib/tasks
108
+ * Dispatch state behavior to the superclass if it's undefined for a particular state [Sandro Turriate and Tim Pope]
109
+ * Fix state / event names not supporting i18n in ActiveRecord
110
+ * Fix original ActiveRecord::Observer#update not being used for non-state_machine callbacks [Jeremy Wells]
111
+ * Add support for ActiveRecord 3.0
112
+ * Fix without_{name} scopes not quoting columns in ActiveRecord [Jon Evans]
113
+ * Fix without_{name} scopes not scoping columns to the table in ActiveRecord and Sequel [Jon Evans]
114
+ * Fix custom state attributes not being marked properly as changed in ActiveRecord
115
+ * Fix tracked attributes changes in ActiveRecord / DataMapper integrations not working correctly for non-loopbacks [Joe Lind]
116
+ * Fix plural scope names being incorrect for DataMapper 0.9.4 - 0.9.6
117
+ * Fix deprecation warnings for ruby-graphviz 0.9.0+
118
+ * Add support for ActiveRecord 2.0.*
119
+ * Fix nil states being overwritten when they're explicitly set in ORM integrations
120
+ * Fix default states not getting set in ORM integrations if the column has a default
121
+ * Fix event transitions being kept around while running actions/callbacks, sometimes preventing object marshalling
122
+
123
+ == 0.8.0 / 2009-08-15
124
+
125
+ * Add support for DataMapper 0.10.0
126
+ * Always interpet nil return values from actions as failed attempts
127
+ * Fix loopbacks not causing records to save in ORM integrations if no other fields were changed
128
+ * Fix events not failing with useful errors when an object's state is invalid
129
+ * Use more friendly NoMethodError messages for state-driven behaviors
130
+ * Fix before_transition callbacks getting run twice when using event attributes in ORM integrations
131
+ * Add the ability to query for the availability of specific transitions on an object
132
+ * Allow after_transition callbacks to be explicitly run on failed attempts
133
+ * By default, don't run after_transition callbacks on failed attempts
134
+ * Fix not allowing multiple methods to be specified as arguments in callbacks
135
+ * Fix initial states being set when loading records from the database in Sequel integration
136
+ * Allow static initial states to be set earlier in the initialization of an object
137
+ * Use friendly validation errors for nil states
138
+ * Fix states not being validated properly when using custom names in ActiveRecord / DataMapper integrations
139
+
140
+ == 0.7.6 / 2009-06-17
141
+
142
+ * Allow multiple state machines on the same class to target the same attribute
143
+ * Add support for :attribute to customize the attribute target, assuming the name is the first argument of #state_machine
144
+ * Simplify reading from / writing to machine-related attributes on objects
145
+ * Fix locale for ActiveRecord getting added to the i18n load path multiple times [Reiner Dieterich]
146
+ * Fix callbacks, guards, and state-driven behaviors not always working on tainted classes [Brandon Dimcheff]
147
+ * Use Ruby 1.9's built-in Object#instance_exec for bound callbacks when it's available
148
+ * Improve performance of cached dynamic state lookups by 25%
149
+
150
+ == 0.7.5 / 2009-05-25
151
+
152
+ * Add built-in caching for dynamic state values when the value only needs to be generated once
153
+ * Fix flawed example for using record ids as state values
154
+ * Don't evaluate state values until they're actually used in an object instance
155
+ * Make it easier to use event attributes for actions defined in the same class as the state machine
156
+ * Fix #save/save! running transitions in ActiveRecord integrations even when a machine's action is not :save
157
+
158
+ == 0.7.4 / 2009-05-23
159
+
160
+ * Fix #save! not firing event attributes properly in ActiveRecord integrations
161
+ * Fix log files being included in gems
162
+
163
+ == 0.7.3 / 2009-04-25
164
+
165
+ * Require DataMapper version be >= 0.9.4
166
+ * Explicitly load Sequel's built-in inflector (>= 2.12.0) for scope names
167
+ * Don't use qualified name for event attributes
168
+ * Fix #valid? being defined for DataMapper resources when dm-validations isn't loaded
169
+ * Add auto-validation of values allowed for the state attribute in ORM integrations
170
+
171
+ == 0.7.2 / 2009-04-08
172
+
173
+ * Add support for running multiple methods in a callback without using blocks
174
+ * Add more flexibility around how callbacks are defined
175
+ * Add security documentation around mass-assignment in ORM integrations
176
+ * Fix event attribute transitions being publicly accessible
177
+
178
+ == 0.7.1 / 2009-04-05
179
+
180
+ * Fix machines failing to generate graphs when run from Merb tasks
181
+
182
+ == 0.7.0 / 2009-04-04
183
+
184
+ * Add #{attribute}_event for automatically firing events when the object's action is called
185
+ * Make it easier to override state-driven behaviors
186
+ * Rollback state changes when the action fails during transitions
187
+ * Use :messages instead of :invalid_message for customizing validation errors
188
+ * Use more human-readable validation errors
189
+ * Add support for more ActiveRecord observer hooks
190
+ * Add support for targeting multiple specific state machines in DataMapper observer hooks
191
+ * Don't pass the result of the action as an argument to callbacks (access via Transition#result)
192
+ * Fix incorrect results being used when running transitions in parallel
193
+ * Fix transition args not being set when run in parallel
194
+ * Allow callback terminators to be set on an application-wide basis
195
+ * Only catch :halt during before / after transition callbacks
196
+ * Fix ActiveRecord predicates being overwritten if they're already defined in the class
197
+ * Allow machine options to be set on an integration-wide basis
198
+ * Turn transactions off by default in DataMapper integrations
199
+ * Add support for configuring the use of transactions
200
+ * Simplify reading/writing of attributes
201
+ * Simplify access to state machines via #state_machine(:attribute) without generating dupes
202
+ * Fix assumptions that dm-validations is always available in DataMapper integration
203
+ * Automatically define DataMapper properties for machine attributes if they don't exist
204
+ * Add Transition#qualified_event, #qualified_from_name, and #qualified_to_name
205
+ * Add #fire_events / #fire_events! for running events on multiple state machines in parallel
206
+ * Rename next_#{event}_transition to #{event}_transition
207
+ * Add #{attribute}_transitions for getting the list of transitions that can be run on an object
208
+ * Add #{attribute}_events for getting the list of events that can be fired on an object
209
+ * Use generated non-bang event when running bang version so that overriding one affects the other
210
+ * Provide access to arguments passed into an event from transition callbacks via Transition#args
211
+
212
+ == 0.6.3 / 2009-03-10
213
+
214
+ * Add support for customizing the graph's orientation
215
+ * Use the standard visualizations for initial (open arrow) and final (double circle) states
216
+ * Highlight final states in GraphViz drawings
217
+
218
+ == 0.6.2 / 2009-03-08
219
+
220
+ * Make it easier to override generated instance / class methods
221
+
222
+ == 0.6.1 / 2009-03-07
223
+
224
+ * Add i18n support for ActiveRecord validation errors
225
+ * Add a validation error when failing to transition for ActiveRecord / DataMapper / Sequel integrations
226
+
227
+ == 0.6.0 / 2009-03-03
228
+
229
+ * Allow multiple conditions for callbacks / class behaviors
230
+ * Add support for state-driven class behavior with :if/:unless options
231
+ * Alias Machine#event as Machine#on
232
+ * Fix nil from/to states not being handled properly
233
+ * Simplify hooking callbacks into loopbacks
234
+ * Add simplified transition/callback requirement syntax
235
+
236
+ == 0.5.2 / 2009-02-17
237
+
238
+ * Improve pretty-print of events
239
+ * Simplify state/event matching design, improving guard performance by 30%
240
+ * Add better error notification when conflicting guard options are defined
241
+ * Fix scope name pluralization not being applied correctly
242
+
243
+ == 0.5.1 / 2009-02-11
244
+
245
+ * Allow states to be drawn as ellipses to accommodate long names
246
+ * Fix rake tasks not being registered in Rails/Merb applications
247
+ * Never automatically define machine attribute accessors when using an integration
248
+
249
+ == 0.5.0 / 2009-01-11
250
+
251
+ * Add to_name and from_name to transition objects
252
+ * Add nicely formatted #inspect for transitions
253
+ * Fix ActiveRecord integrations failing when the database doesn't exist yet
254
+ * Fix states not being drawn in GraphViz graphs in the correct order
255
+ * Add nicely formatted #inspect for states and events
256
+ * Simplify machine context-switching
257
+ * Store events/states in enumerable node collections
258
+ * No longer allow subclasses to change the integration
259
+ * Move fire! action logic into the Event class (no longer calls fire action on the object)
260
+ * Allow states in subclasses to have different values
261
+ * Recommend that all states be referenced as symbols instead of strings
262
+ * All states must now be named (and can be associated with other value types)
263
+ * Add support for customizing the actual stored value for a state
264
+ * Add compatibility with Ruby 1.9+
265
+
266
+ == 0.4.3 / 2008-12-28
267
+
268
+ * Allow dm-observer integration to be optional
269
+ * Fix non-lambda callbacks not working for DataMapper/Sequel
270
+
271
+ == 0.4.2 / 2008-12-28
272
+
273
+ * Fix graphs not being drawn the same way consistently
274
+ * Add support for sharing transitions across multiple events
275
+ * Add support for state-driven behavior
276
+ * Simplify initialize hooks, requiring super to be called instead
277
+ * Add :namespace option for generated state predicates / event methods
278
+
279
+ == 0.4.1 / 2008-12-16
280
+
281
+ * Fix nil states not being handled properly in guards, known states, or visualizations
282
+ * Fix the same node being used for different dynamic states in GraphViz output
283
+ * Always include initial state in the list of known states even if it's dynamic
284
+ * Use consistent naming scheme for dynamic states in GraphViz output
285
+ * Allow blocks to be directly passed into machine class
286
+ * Fix attribute predicates not working on attributes that represent columns in ActiveRecord
287
+
288
+ == 0.4.0 / 2008-12-14
289
+
290
+ * Remove the PluginAWeek namespace
291
+ * Add generic attribute predicate (e.g. "#{attribute}?(state_name)") and state predicates (e.g. "#{state}?")
292
+ * Add Sequel support
293
+ * Fix aliasing :initialize on ActiveRecord models causing warnings when the environment is reloaded
294
+ * Fix ActiveRecord state machines trying to query the database on unmigrated models
295
+ * Fix initial states not getting set when the current value is an empty string [Aaron Gibralter]
296
+ * Add rake tasks for generating graphviz files for state machines [Nate Murray]
297
+ * Fix initial state not being included in list of known states
298
+ * Add other_states directive for defining additional states not referenced in transitions or callbacks [Pete Forde]
299
+ * Add next_#{event}_transition for getting the next transition that would be performed if the event were invoked
300
+ * Add the ability to override the pluralized name of an attribute for creating scopes
301
+ * Add the ability to halt callback chains by: throw :halt
302
+ * Add support for dynamic to states in transitions (e.g. :to => lambda {Time.now})
303
+ * Add support for using real blocks in before_transition/after_transition calls instead of using the :do option
304
+ * Add DataMapper support
305
+ * Include states referenced in transition callbacks in the list of a machine's known states
306
+ * Only generate the known states for a machine on demand, rather than calculating beforehand
307
+ * Add the ability to skip state change actions during a transition (e.g. vehicle.ignite(false))
308
+ * Add the ability for the state change action (e.g. +save+ for ActiveRecord) to be configurable
309
+ * Allow state machines to be defined on *any* Ruby class, not just ActiveRecord (removes all external dependencies)
310
+ * Refactor transitions, guards, and callbacks for better organization/design
311
+ * Use a class containing the transition context in callbacks, rather than an ordered list of each individual attribute
312
+ * Add without_#{attribute} named scopes (opposite of the existing with_#{attribute} named scopes) [Sean O'Brien]
313
+
314
+ == 0.3.1 / 2008-10-26
315
+
316
+ * Fix the initial state not getting set when the state attribute is mass-assigned but protected
317
+ * Change how the base module is included to prevent namespacing conflicts
318
+
319
+ == 0.3.0 / 2008-09-07
320
+
321
+ * No longer allow additional arguments to be passed into event actions
322
+ * Add support for can_#{event}? for checking whether an event can be fired based on the current state of the record
323
+ * Don't use callbacks for performing transitions
324
+ * Fix state machines in subclasses not knowing what states/events/transitions were defined by superclasses
325
+ * Replace all before/after_exit/enter/loopback callback hooks and :before/:after options for events with before_transition/after_transition callbacks, e.g.
326
+
327
+ before_transition :from => 'parked', :do => :lock_doors # was before_exit :parked, :lock_doors
328
+ after_transition :on => 'ignite', :do => :turn_on_radio # was event :ignite, :after => :turn_on_radio do
329
+
330
+ * Always save when an event is fired even if it results in a loopback [Jürgen Strobel]
331
+ * Ensure initial state callbacks are invoked in the proper order when an event is fired on a new record
332
+ * Add before_loopback and after_loopback hooks [Jürgen Strobel]
333
+
334
+ == 0.2.1 / 2008-07-05
335
+
336
+ * Add more descriptive exceptions
337
+ * Assume the default state attribute is "state" if one is not provided
338
+ * Add :except_from option for transitions if you want to blacklist states
339
+ * Add PluginAWeek::StateMachine::Machine#states
340
+ * Add PluginAWeek::StateMachine::Event#transitions
341
+ * Allow creating transitions with no from state (effectively allowing the transition for *any* from state)
342
+ * Reduce the number of objects created for each transition
343
+
344
+ == 0.2.0 / 2008-06-29
345
+
346
+ * Add a non-bang version of events (e.g. park) that will return a boolean value for success
347
+ * Raise an exception if the bang version of events are used (e.g. park!) and no transition is successful
348
+ * Change callbacks to act a little more like ActiveRecord
349
+ * Avoid using string evaluation for dynamic methods
350
+
351
+ == 0.1.1 / 2008-06-22
352
+
353
+ * Remove log files from gems
354
+
355
+ == 0.1.0 / 2008-05-05
356
+
357
+ * Completely rewritten from scratch
358
+ * Renamed to state_machine
359
+ * Removed database dependencies
360
+ * Removed models in favor of an attribute-agnostic design
361
+ * Use ActiveSupport::Callbacks instead of eval_call
362
+ * Remove dry_transaction_rollbacks dependencies
363
+ * Added functional tests
364
+ * Updated documentation
365
+
366
+ == 0.0.1 / 2007-09-26
367
+
368
+ * Add dependency on custom_callbacks
369
+ * Move test fixtures out of the test application root directory
370
+ * Improve documentation
371
+ * Remove the StateExtension module in favor of adding singleton methods to the stateful class
372
+ * Convert dos newlines to unix newlines
373
+ * Fix error message when a given event can't be found in the database
374
+ * Add before_#{action} and #{action} callbacks when an event is performed
375
+ * All state and event callbacks can now explicitly return false in order to cancel the action
376
+ * Refactor ActiveState callback creation
377
+ * Refactor unit tests so that they use mock classes instead of themselves
378
+ * Allow force_reload option to be set in the state association
379
+ * Don't save the entire model when updating the state_id
380
+ * Raise exception if a class tries to define a state more than once
381
+ * Add tests for PluginAWeek::Has::States::ActiveState
382
+ * Refactor active state/active event creation
383
+ * Fix owner_type not being set correctly in active states/events of subclasses
384
+ * Allow subclasses to override the initial state
385
+ * Fix problem with migrations using default null when column cannot be null
386
+ * Moved deadline support into a separate plugin (has_state_deadlines).
387
+ * Added many more unit tests.
388
+ * Simplified many of the interfaces for maintainability.
389
+ * Added support for turning off recording state changes.
390
+ * Removed the short_description and long_description columns, in favor of an optional human_name column.
391
+ * Fixed not overriding the correct equality methods in the StateTransition class.
392
+ * Added to_sym to State and Event.
393
+ * State#name and Event#name now return the string version of the name instead of the symbol version.
394
+ * Added State#human_name and Event#human_name to automatically figure out what the human name is if it isn't specified in the table.
395
+ * Updated manual rollbacks to use the new Rails edge api (ActiveRecord::Rollback exception).
396
+ * Moved StateExtension class into a separate file in order to help keep the has_state files clean.
397
+ * Renamed InvalidState and InvalidEvent exceptions to StateNotFound and EventNotFound in order to follow the ActiveRecord convention (i.e. RecordNotFound).
398
+ * Added StateNotActive and EventNotActive exceptions to help differentiate between states which don't exist and states which weren't defined in the class.
399
+ * Added support for defining callbacks like so:
400
+
401
+ def before_exit_parked
402
+ end
403
+
404
+ def after_enter_idling
405
+ end
406
+
407
+ * Added support for defining callbacks using class methods:
408
+
409
+ before_exit_parked :fasten_seatbelt
410
+
411
+ * Added event callbacks after the transition has occurred (e.g. after_park)
412
+ * State callbacks no longer receive any of the arguments that were provided in the event action
413
+ * Updated license to include our names.
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2006-2011 Aaron Pfefier
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,717 @@
1
+ == state_machine
2
+
3
+ +state_machine+ adds support for creating state machines for attributes on any
4
+ Ruby class.
5
+
6
+ == Resources
7
+
8
+ API
9
+
10
+ * http://rdoc.info/github/pluginaweek/state_machine/master/frames
11
+
12
+ Bugs
13
+
14
+ * http://pluginaweek.lighthouseapp.com/projects/13288-state_machine
15
+
16
+ Development
17
+
18
+ * http://github.com/pluginaweek/state_machine
19
+
20
+ Source
21
+
22
+ * git://github.com/pluginaweek/state_machine.git
23
+
24
+ == Description
25
+
26
+ State machines make it dead-simple to manage the behavior of a class. Too often,
27
+ the state of an object is kept by creating multiple boolean attributes and
28
+ deciding how to behave based on the values. This can become cumbersome and
29
+ difficult to maintain when the complexity of your class starts to increase.
30
+
31
+ +state_machine+ simplifies this design by introducing the various parts of a real
32
+ state machine, including states, events, transitions, and callbacks. However,
33
+ the api is designed to be so simple you don't even need to know what a
34
+ state machine is :)
35
+
36
+ Some brief, high-level features include:
37
+ * Defining state machines on any Ruby class
38
+ * Multiple state machines on a single class
39
+ * Namespaced state machines
40
+ * before/after/around/failure transition hooks with explicit transition requirements
41
+ * Integration with ActiveModel, ActiveRecord, DataMapper, Mongoid, MongoMapper, and Sequel
42
+ * State predicates
43
+ * State-driven instance / class behavior
44
+ * State values of any data type
45
+ * Dynamically-generated state values
46
+ * Event parallelization
47
+ * Attribute-based event transitions
48
+ * Path analysis
49
+ * Inheritance
50
+ * Internationalization
51
+ * GraphViz visualization creator
52
+
53
+ Examples of the usage patterns for some of the above features are shown below.
54
+ You can find much more detailed documentation in the actual API.
55
+
56
+ == Usage
57
+
58
+ === Example
59
+
60
+ Below is an example of many of the features offered by this plugin, including:
61
+ * Initial states
62
+ * Namespaced states
63
+ * Transition callbacks
64
+ * Conditional transitions
65
+ * State-driven instance behavior
66
+ * Customized state values
67
+ * Parallel events
68
+ * Path analysis
69
+
70
+ Class definition:
71
+
72
+ class Vehicle
73
+ attr_accessor :seatbelt_on, :time_used
74
+
75
+ state_machine :state, :initial => :parked do
76
+ before_transition :parked => any - :parked, :do => :put_on_seatbelt
77
+
78
+ after_transition :on => :crash, :do => :tow
79
+ after_transition :on => :repair, :do => :fix
80
+ after_transition any => :parked do |vehicle, transition|
81
+ vehicle.seatbelt_on = false
82
+ end
83
+
84
+ after_failure :on => :ignite, :do => :log_start_failure
85
+
86
+ around_transition do |vehicle, transition, block|
87
+ start = Time.now
88
+ block.call
89
+ vehicle.time_used += Time.now - start
90
+ end
91
+
92
+ event :park do
93
+ transition [:idling, :first_gear] => :parked
94
+ end
95
+
96
+ event :ignite do
97
+ transition :stalled => same, :parked => :idling
98
+ end
99
+
100
+ event :idle do
101
+ transition :first_gear => :idling
102
+ end
103
+
104
+ event :shift_up do
105
+ transition :idling => :first_gear, :first_gear => :second_gear, :second_gear => :third_gear
106
+ end
107
+
108
+ event :shift_down do
109
+ transition :third_gear => :second_gear, :second_gear => :first_gear
110
+ end
111
+
112
+ event :crash do
113
+ transition all - [:parked, :stalled] => :stalled, :unless => :auto_shop_busy?
114
+ end
115
+
116
+ event :repair do
117
+ # The first transition that matches the state and passes its conditions
118
+ # will be used
119
+ transition :stalled => :parked, :if => :auto_shop_busy?
120
+ transition :stalled => same
121
+ end
122
+
123
+ state :parked do
124
+ def speed
125
+ 0
126
+ end
127
+ end
128
+
129
+ state :idling, :first_gear do
130
+ def speed
131
+ 10
132
+ end
133
+ end
134
+
135
+ state :second_gear do
136
+ def speed
137
+ 20
138
+ end
139
+ end
140
+ end
141
+
142
+ state_machine :alarm_state, :initial => :active, :namespace => 'alarm' do
143
+ event :enable do
144
+ transition all => :active
145
+ end
146
+
147
+ event :disable do
148
+ transition all => :off
149
+ end
150
+
151
+ state :active, :value => 1
152
+ state :off, :value => 0
153
+ end
154
+
155
+ def initialize
156
+ @seatbelt_on = false
157
+ @time_used = 0
158
+ super() # NOTE: This *must* be called, otherwise states won't get initialized
159
+ end
160
+
161
+ def put_on_seatbelt
162
+ @seatbelt_on = true
163
+ end
164
+
165
+ def auto_shop_busy?
166
+ false
167
+ end
168
+
169
+ def tow
170
+ # tow the vehicle
171
+ end
172
+
173
+ def fix
174
+ # get the vehicle fixed by a mechanic
175
+ end
176
+
177
+ def log_start_failure
178
+ # log a failed attempt to start the vehicle
179
+ end
180
+ end
181
+
182
+ *Note* the comment made on the +initialize+ method in the class. In order for
183
+ state machine attributes to be properly initialized, <tt>super()</tt> must be called.
184
+ See StateMachine::MacroMethods for more information about this.
185
+
186
+ Using the above class as an example, you can interact with the state machine
187
+ like so:
188
+
189
+ vehicle = Vehicle.new # => #<Vehicle:0xb7cf4eac @state="parked", @seatbelt_on=false>
190
+ vehicle.state # => "parked"
191
+ vehicle.state_name # => :parked
192
+ vehicle.human_state_name # => "parked"
193
+ vehicle.parked? # => true
194
+ vehicle.can_ignite? # => true
195
+ vehicle.ignite_transition # => #<StateMachine::Transition attribute=:state event=:ignite from="parked" from_name=:parked to="idling" to_name=:idling>
196
+ vehicle.state_events # => [:ignite]
197
+ vehicle.state_transitions # => [#<StateMachine::Transition attribute=:state event=:ignite from="parked" from_name=:parked to="idling" to_name=:idling>]
198
+ vehicle.speed # => 0
199
+
200
+ vehicle.ignite # => true
201
+ vehicle.parked? # => false
202
+ vehicle.idling? # => true
203
+ vehicle.speed # => 10
204
+ vehicle # => #<Vehicle:0xb7cf4eac @state="idling", @seatbelt_on=true>
205
+
206
+ vehicle.shift_up # => true
207
+ vehicle.speed # => 10
208
+ vehicle # => #<Vehicle:0xb7cf4eac @state="first_gear", @seatbelt_on=true>
209
+
210
+ vehicle.shift_up # => true
211
+ vehicle.speed # => 20
212
+ vehicle # => #<Vehicle:0xb7cf4eac @state="second_gear", @seatbelt_on=true>
213
+
214
+ # The bang (!) operator can raise exceptions if the event fails
215
+ vehicle.park! # => StateMachine::InvalidTransition: Cannot transition state via :park from :second_gear
216
+
217
+ # Generic state predicates can raise exceptions if the value does not exist
218
+ vehicle.state?(:parked) # => false
219
+ vehicle.state?(:invalid) # => IndexError: :invalid is an invalid name
220
+
221
+ # Namespaced machines have uniquely-generated methods
222
+ vehicle.alarm_state # => 1
223
+ vehicle.alarm_state_name # => :active
224
+
225
+ vehicle.can_disable_alarm? # => true
226
+ vehicle.disable_alarm # => true
227
+ vehicle.alarm_state # => 0
228
+ vehicle.alarm_state_name # => :off
229
+ vehicle.can_enable_alarm? # => true
230
+
231
+ vehicle.alarm_off? # => true
232
+ vehicle.alarm_active? # => false
233
+
234
+ # Events can be fired in parallel
235
+ vehicle.fire_events(:shift_down, :enable_alarm) # => true
236
+ vehicle.state_name # => :first_gear
237
+ vehicle.alarm_state_name # => :active
238
+
239
+ vehicle.fire_events!(:ignite, :enable_alarm) # => StateMachine::InvalidTransition: Cannot run events in parallel: ignite, enable_alarm
240
+
241
+ # Human-friendly names can be accessed for states/events
242
+ Vehicle.human_state_name(:first_gear) # => "first gear"
243
+ Vehicle.human_alarm_state_name(:active) # => "active"
244
+
245
+ Vehicle.human_state_event_name(:shift_down) # => "shift down"
246
+ Vehicle.human_alarm_state_event_name(:enable) # => "enable"
247
+
248
+ # Available transition paths can be analyzed for an object
249
+ vehicle.state_paths # => [[#<StateMachine::Transition ...], [#<StateMachine::Transition ...], ...]
250
+ vehicle.state_paths.to_states # => [:parked, :idling, :first_gear, :stalled, :second_gear, :third_gear]
251
+ vehicle.state_paths.events # => [:park, :ignite, :shift_up, :idle, :crash, :repair, :shift_down]
252
+
253
+ # Find all paths that start and end on certain states
254
+ vehicle.state_paths(:from => :parked, :to => :first_gear) # => [[
255
+ # #<StateMachine::Transition attribute=:state event=:ignite from="parked" ...>,
256
+ # #<StateMachine::Transition attribute=:state event=:shift_up from="idling" ...>
257
+ # ]]
258
+
259
+ == Integrations
260
+
261
+ In addition to being able to define state machines on all Ruby classes, a set of
262
+ out-of-the-box integrations are available for some of the more popular Ruby
263
+ libraries. These integrations add library-specific behavior, allowing for state
264
+ machines to work more tightly with the conventions defined by those libraries.
265
+
266
+ The integrations currently available include:
267
+ * ActiveModel classes
268
+ * ActiveRecord models
269
+ * DataMapper resources
270
+ * Mongoid models
271
+ * MongoMapper models
272
+ * Sequel models
273
+
274
+ A brief overview of these integrations is described below.
275
+
276
+ === ActiveModel
277
+
278
+ The ActiveModel integration is useful for both standalone usage and for providing
279
+ the base implementation for ORMs which implement the ActiveModel API. This
280
+ integration adds support for validation errors, dirty attribute tracking, and
281
+ observers. For example,
282
+
283
+ class Vehicle
284
+ include ActiveModel::Dirty
285
+ include ActiveModel::Validations
286
+ include ActiveModel::Observing
287
+
288
+ attr_accessor :state
289
+ define_attribute_methods [:state]
290
+
291
+ state_machine :initial => :parked do
292
+ before_transition :parked => any - :parked, :do => :put_on_seatbelt
293
+ after_transition any => :parked do |vehicle, transition|
294
+ vehicle.seatbelt = 'off'
295
+ end
296
+ around_transition :benchmark
297
+
298
+ event :ignite do
299
+ transition :parked => :idling
300
+ end
301
+
302
+ state :first_gear, :second_gear do
303
+ validates_presence_of :seatbelt_on
304
+ end
305
+ end
306
+
307
+ def put_on_seatbelt
308
+ ...
309
+ end
310
+
311
+ def benchmark
312
+ ...
313
+ yield
314
+ ...
315
+ end
316
+ end
317
+
318
+ class VehicleObserver < ActiveModel::Observer
319
+ # Callback for :ignite event *before* the transition is performed
320
+ def before_ignite(vehicle, transition)
321
+ # log message
322
+ end
323
+
324
+ # Generic transition callback *after* the transition is performed
325
+ def after_transition(vehicle, transition)
326
+ Audit.log(vehicle, transition)
327
+ end
328
+
329
+ # Generic callback after the transition fails to perform
330
+ def after_failure_to_transition(vehicle, transition)
331
+ Audit.error(vehicle, transition)
332
+ end
333
+ end
334
+
335
+ For more information about the various behaviors added for ActiveModel state
336
+ machines and how to build new integrations that use ActiveModel, see
337
+ StateMachine::Integrations::ActiveModel.
338
+
339
+ === ActiveRecord
340
+
341
+ The ActiveRecord integration adds support for database transactions, automatically
342
+ saving the record, named scopes, validation errors, and observers. For example,
343
+
344
+ class Vehicle < ActiveRecord::Base
345
+ state_machine :initial => :parked do
346
+ before_transition :parked => any - :parked, :do => :put_on_seatbelt
347
+ after_transition any => :parked do |vehicle, transition|
348
+ vehicle.seatbelt = 'off'
349
+ end
350
+ around_transition :benchmark
351
+
352
+ event :ignite do
353
+ transition :parked => :idling
354
+ end
355
+
356
+ state :first_gear, :second_gear do
357
+ validates_presence_of :seatbelt_on
358
+ end
359
+ end
360
+
361
+ def put_on_seatbelt
362
+ ...
363
+ end
364
+
365
+ def benchmark
366
+ ...
367
+ yield
368
+ ...
369
+ end
370
+ end
371
+
372
+ class VehicleObserver < ActiveRecord::Observer
373
+ # Callback for :ignite event *before* the transition is performed
374
+ def before_ignite(vehicle, transition)
375
+ # log message
376
+ end
377
+
378
+ # Generic transition callback *after* the transition is performed
379
+ def after_transition(vehicle, transition)
380
+ Audit.log(vehicle, transition)
381
+ end
382
+ end
383
+
384
+ For more information about the various behaviors added for ActiveRecord state
385
+ machines, see StateMachine::Integrations::ActiveRecord.
386
+
387
+ === DataMapper
388
+
389
+ Like the ActiveRecord integration, the DataMapper integration adds support for
390
+ database transactions, automatically saving the record, named scopes, Extlib-like
391
+ callbacks, validation errors, and observers. For example,
392
+
393
+ class Vehicle
394
+ include DataMapper::Resource
395
+
396
+ property :id, Serial
397
+ property :state, String
398
+
399
+ state_machine :initial => :parked do
400
+ before_transition :parked => any - :parked, :do => :put_on_seatbelt
401
+ after_transition any => :parked do |transition|
402
+ self.seatbelt = 'off' # self is the record
403
+ end
404
+ around_transition :benchmark
405
+
406
+ event :ignite do
407
+ transition :parked => :idling
408
+ end
409
+
410
+ state :first_gear, :second_gear do
411
+ validates_presence_of :seatbelt_on
412
+ end
413
+ end
414
+
415
+ def put_on_seatbelt
416
+ ...
417
+ end
418
+
419
+ def benchmark
420
+ ...
421
+ yield
422
+ ...
423
+ end
424
+ end
425
+
426
+ class VehicleObserver
427
+ include DataMapper::Observer
428
+
429
+ observe Vehicle
430
+
431
+ # Callback for :ignite event *before* the transition is performed
432
+ before_transition :on => :ignite do |transition|
433
+ # log message (self is the record)
434
+ end
435
+
436
+ # Generic transition callback *after* the transition is performed
437
+ after_transition do |transition|
438
+ Audit.log(self, transition) # self is the record
439
+ end
440
+
441
+ around_transition do |transition, block|
442
+ # mark start time
443
+ block.call
444
+ # mark stop time
445
+ end
446
+
447
+ # Generic callback after the transition fails to perform
448
+ after_transition_failure do |transition|
449
+ Audit.log(self, transition) # self is the record
450
+ end
451
+ end
452
+
453
+ *Note* that the DataMapper::Observer integration is optional and only available
454
+ when the dm-observer library is installed.
455
+
456
+ For more information about the various behaviors added for DataMapper state
457
+ machines, see StateMachine::Integrations::DataMapper.
458
+
459
+ === Mongoid
460
+
461
+ The Mongoid integration adds support for automatically saving the record,
462
+ basic scopes, validation errors, and observers. For example,
463
+
464
+ class Vehicle
465
+ include Mongoid::Document
466
+
467
+ state_machine :initial => :parked do
468
+ before_transition :parked => any - :parked, :do => :put_on_seatbelt
469
+ after_transition any => :parked do |vehicle, transition|
470
+ vehicle.seatbelt = 'off' # self is the record
471
+ end
472
+ around_transition :benchmark
473
+
474
+ event :ignite do
475
+ transition :parked => :idling
476
+ end
477
+
478
+ state :first_gear, :second_gear do
479
+ validates_presence_of :seatbelt_on
480
+ end
481
+ end
482
+
483
+ def put_on_seatbelt
484
+ ...
485
+ end
486
+
487
+ def benchmark
488
+ ...
489
+ yield
490
+ ...
491
+ end
492
+ end
493
+
494
+ class VehicleObserver < Mongoid::Observer
495
+ # Callback for :ignite event *before* the transition is performed
496
+ def before_ignite(vehicle, transition)
497
+ # log message
498
+ end
499
+
500
+ # Generic transition callback *after* the transition is performed
501
+ def after_transition(vehicle, transition)
502
+ Audit.log(vehicle, transition)
503
+ end
504
+ end
505
+
506
+ For more information about the various behaviors added for Mongoid state
507
+ machines, see StateMachine::Integrations::Mongoid.
508
+
509
+ === MongoMapper
510
+
511
+ The MongoMapper integration adds support for automatically saving the record,
512
+ basic scopes, validation errors and callbacks. For example,
513
+
514
+ class Vehicle
515
+ include MongoMapper::Document
516
+
517
+ state_machine :initial => :parked do
518
+ before_transition :parked => any - :parked, :do => :put_on_seatbelt
519
+ after_transition any => :parked do |vehicle, transition|
520
+ vehicle.seatbelt = 'off' # self is the record
521
+ end
522
+ around_transition :benchmark
523
+
524
+ event :ignite do
525
+ transition :parked => :idling
526
+ end
527
+
528
+ state :first_gear, :second_gear do
529
+ validates_presence_of :seatbelt_on
530
+ end
531
+ end
532
+
533
+ def put_on_seatbelt
534
+ ...
535
+ end
536
+
537
+ def benchmark
538
+ ...
539
+ yield
540
+ ...
541
+ end
542
+ end
543
+
544
+ For more information about the various behaviors added for MongoMapper state
545
+ machines, see StateMachine::Integrations::MongoMapper.
546
+
547
+ === Sequel
548
+
549
+ Like the ActiveRecord integration, the Sequel integration adds support for
550
+ database transactions, automatically saving the record, named scopes, validation
551
+ errors and callbacks. For example,
552
+
553
+ class Vehicle < Sequel::Model
554
+ state_machine :initial => :parked do
555
+ before_transition :parked => any - :parked, :do => :put_on_seatbelt
556
+ after_transition any => :parked do |transition|
557
+ self.seatbelt = 'off' # self is the record
558
+ end
559
+ around_transition :benchmark
560
+
561
+ event :ignite do
562
+ transition :parked => :idling
563
+ end
564
+
565
+ state :first_gear, :second_gear do
566
+ validates_presence_of :seatbelt_on
567
+ end
568
+ end
569
+
570
+ def put_on_seatbelt
571
+ ...
572
+ end
573
+
574
+ def benchmark
575
+ ...
576
+ yield
577
+ ...
578
+ end
579
+ end
580
+
581
+ For more information about the various behaviors added for Sequel state
582
+ machines, see StateMachine::Integrations::Sequel.
583
+
584
+ == Compatibility
585
+
586
+ Although state_machine introduces a simplified syntax, it still remains
587
+ backwards compatible with previous versions and other state-related libraries.
588
+ For example, transitions and callbacks can continue to be defined like so:
589
+
590
+ class Vehicle
591
+ state_machine :initial => :parked do
592
+ before_transition :from => :parked, :except_to => :parked, :do => :put_on_seatbelt
593
+ after_transition :to => :parked do |transition|
594
+ self.seatbelt = 'off' # self is the record
595
+ end
596
+
597
+ event :ignite do
598
+ transition :from => :parked, :to => :idling
599
+ end
600
+ end
601
+ end
602
+
603
+ Although this verbose syntax will most likely always be supported, it is
604
+ recommended that any state machines eventually migrate to the syntax introduced
605
+ in version 0.6.0.
606
+
607
+ == Tools
608
+
609
+ === Generating graphs
610
+
611
+ This library comes with built-in support for generating di-graphs based on the
612
+ events, states, and transitions defined for a state machine using GraphViz[http://www.graphviz.org].
613
+ This requires that both the <tt>ruby-graphviz</tt> gem and graphviz library be
614
+ installed on the system.
615
+
616
+ ==== Examples
617
+
618
+ To generate a graph for a specific file / class:
619
+
620
+ rake state_machine:draw FILE=vehicle.rb CLASS=Vehicle
621
+
622
+ To save files to a specific path:
623
+
624
+ rake state_machine:draw FILE=vehicle.rb CLASS=Vehicle TARGET=files
625
+
626
+ To customize the image format / orientation:
627
+
628
+ rake state_machine:draw FILE=vehicle.rb CLASS=Vehicle FORMAT=jpg ORIENTATION=landscape
629
+
630
+ To generate multiple state machine graphs:
631
+
632
+ rake state_machine:draw FILE=vehicle.rb,car.rb CLASS=Vehicle,Car
633
+
634
+ *Note* that this will generate a different file for every state machine defined
635
+ in the class. The generated files will use an output filename of the format
636
+ #{class_name}_#{machine_name}.#{format}.
637
+
638
+ For examples of actual images generated using this task, see those under the
639
+ examples folder.
640
+
641
+ ==== Ruby on Rails Integration
642
+
643
+ There is a special integration Rake task for generating state machines for
644
+ classes used in a Ruby on Rails application. This task will load the application
645
+ environment, meaning that it's unnecessary to specify the actual file to load.
646
+
647
+ For example,
648
+
649
+ rake state_machine:draw CLASS=Vehicle
650
+
651
+ If you are using this library as a gem in Rails 2.x, the following must be added
652
+ to the end of your application's Rakefile in order for the above task to work:
653
+
654
+ require 'tasks/state_machine'
655
+
656
+ If you are using Rails 3.0+, you must also add the following to your
657
+ application's Gemfile:
658
+
659
+ gem 'ruby-graphviz', :require => 'graphviz'
660
+
661
+ ==== Merb Integration
662
+
663
+ Like Ruby on Rails, there is a special integration Rake task for generating
664
+ state machines for classes used in a Merb application. This task will load the
665
+ application environment, meaning that it's unnecessary to specify the actual
666
+ files to load.
667
+
668
+ For example,
669
+
670
+ rake state_machine:draw CLASS=Vehicle
671
+
672
+ === Interactive graphs
673
+
674
+ Jean Bovet's {Visual Automata Simulator}[http://www.cs.usfca.edu/~jbovet/vas.html]
675
+ is a great tool for "simulating, visualizing and transforming finite state
676
+ automata and Turing Machines". It can help in the creation of states and events
677
+ for your models. It is cross-platform, written in Java.
678
+
679
+ == Testing
680
+
681
+ To run the core test suite (does *not* test any of the integrations):
682
+
683
+ rake test
684
+
685
+ Test specific versions of integrations like so:
686
+
687
+ rake test INTEGRATION=active_model VERSION=3.0.0
688
+ rake test INTEGRATION=active_record VERSION=2.0.0
689
+ rake test INTEGRATION=data_mapper VERSION=0.9.4
690
+ rake test INTEGRATION=mongoid VERSION=2.0.0
691
+ rake test INTEGRATION=mongo_mapper VERSION=0.5.5
692
+ rake test INTEGRATION=sequel VERSION=2.8.0
693
+
694
+ == Caveats
695
+
696
+ The following caveats should be noted when using state_machine:
697
+
698
+ * DataMapper: Attribute-based event transitions are disabled when dm-validations 0.9.4 - 0.9.6 is in use
699
+ * Overridden event methods won't get invoked when using attribute-based event transitions
700
+ * around_transition callbacks in ORM integrations won't work on JRuby since it doesn't support continuations
701
+
702
+ == Dependencies
703
+
704
+ * Ruby 1.8.6 or later
705
+
706
+ If using specific integrations:
707
+
708
+ * ActiveModel[http://rubyonrails.org] integration: 3.0.0 or later
709
+ * ActiveRecord[http://rubyonrails.org] integration: 2.0.0 or later
710
+ * DataMapper[http://datamapper.org] integration: 0.9.4 or later
711
+ * Mongoid[http://mongoid.org] integration: 2.0.0 or later
712
+ * MongoMapper[http://mongomapper.com] integration: 0.5.5 or later
713
+ * Sequel[http://sequel.rubyforge.org] integration: 2.8.0 or later
714
+
715
+ If graphing state machine:
716
+
717
+ * ruby-graphviz[http://github.com/glejeune/Ruby-Graphviz]: 0.9.0 or later