hsume2-state_machine 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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