joelind-state_machine 0.8.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 (78) hide show
  1. data/CHANGELOG.rdoc +297 -0
  2. data/LICENSE +20 -0
  3. data/README.rdoc +466 -0
  4. data/Rakefile +98 -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 +388 -0
  28. data/lib/state_machine/assertions.rb +36 -0
  29. data/lib/state_machine/callback.rb +189 -0
  30. data/lib/state_machine/condition_proxy.rb +94 -0
  31. data/lib/state_machine/eval_helpers.rb +67 -0
  32. data/lib/state_machine/event.rb +252 -0
  33. data/lib/state_machine/event_collection.rb +122 -0
  34. data/lib/state_machine/extensions.rb +149 -0
  35. data/lib/state_machine/guard.rb +230 -0
  36. data/lib/state_machine/integrations.rb +68 -0
  37. data/lib/state_machine/integrations/active_record.rb +492 -0
  38. data/lib/state_machine/integrations/active_record/locale.rb +11 -0
  39. data/lib/state_machine/integrations/active_record/observer.rb +41 -0
  40. data/lib/state_machine/integrations/data_mapper.rb +351 -0
  41. data/lib/state_machine/integrations/data_mapper/observer.rb +139 -0
  42. data/lib/state_machine/integrations/sequel.rb +322 -0
  43. data/lib/state_machine/machine.rb +1467 -0
  44. data/lib/state_machine/machine_collection.rb +155 -0
  45. data/lib/state_machine/matcher.rb +123 -0
  46. data/lib/state_machine/matcher_helpers.rb +54 -0
  47. data/lib/state_machine/node_collection.rb +152 -0
  48. data/lib/state_machine/state.rb +249 -0
  49. data/lib/state_machine/state_collection.rb +112 -0
  50. data/lib/state_machine/transition.rb +394 -0
  51. data/tasks/state_machine.rake +1 -0
  52. data/tasks/state_machine.rb +30 -0
  53. data/test/classes/switch.rb +11 -0
  54. data/test/functional/state_machine_test.rb +941 -0
  55. data/test/test_helper.rb +4 -0
  56. data/test/unit/assertions_test.rb +40 -0
  57. data/test/unit/callback_test.rb +455 -0
  58. data/test/unit/condition_proxy_test.rb +328 -0
  59. data/test/unit/eval_helpers_test.rb +120 -0
  60. data/test/unit/event_collection_test.rb +326 -0
  61. data/test/unit/event_test.rb +743 -0
  62. data/test/unit/guard_test.rb +908 -0
  63. data/test/unit/integrations/active_record_test.rb +1374 -0
  64. data/test/unit/integrations/data_mapper_test.rb +962 -0
  65. data/test/unit/integrations/sequel_test.rb +859 -0
  66. data/test/unit/integrations_test.rb +42 -0
  67. data/test/unit/invalid_event_test.rb +7 -0
  68. data/test/unit/invalid_transition_test.rb +7 -0
  69. data/test/unit/machine_collection_test.rb +938 -0
  70. data/test/unit/machine_test.rb +2004 -0
  71. data/test/unit/matcher_helpers_test.rb +37 -0
  72. data/test/unit/matcher_test.rb +155 -0
  73. data/test/unit/node_collection_test.rb +207 -0
  74. data/test/unit/state_collection_test.rb +280 -0
  75. data/test/unit/state_machine_test.rb +31 -0
  76. data/test/unit/state_test.rb +795 -0
  77. data/test/unit/transition_test.rb +1212 -0
  78. metadata +163 -0
data/CHANGELOG.rdoc ADDED
@@ -0,0 +1,297 @@
1
+ == master
2
+
3
+ * Fix nil states being overwritten when they're explicitly set in ORM integrations
4
+ * Fix default states not getting set in ORM integrations if the column has a default
5
+ * Fix event transitions being kept around while running actions/callbacks, sometimes preventing object marshalling
6
+
7
+ == 0.8.0 / 2009-08-15
8
+
9
+ * Add support for DataMapper 0.10.0
10
+ * Always interpet nil return values from actions as failed attempts
11
+ * Fix loopbacks not causing records to save in ORM integrations if no other fields were changed
12
+ * Fix events not failing with useful errors when an object's state is invalid
13
+ * Use more friendly NoMethodError messages for state-driven behaviors
14
+ * Fix before_transition callbacks getting run twice when using event attributes in ORM integrations
15
+ * Add the ability to query for the availability of specific transitions on an object
16
+ * Allow after_transition callbacks to be explicitly run on failed attempts
17
+ * By default, don't run after_transition callbacks on failed attempts
18
+ * Fix not allowing multiple methods to be specified as arguments in callbacks
19
+ * Fix initial states being set when loading records from the database in Sequel integration
20
+ * Allow static initial states to be set earlier in the initialization of an object
21
+ * Use friendly validation errors for nil states
22
+ * Fix states not being validated properly when using custom names in ActiveRecord / DataMapper integrations
23
+
24
+ == 0.7.6 / 2009-06-17
25
+
26
+ * Allow multiple state machines on the same class to target the same attribute
27
+ * Add support for :attribute to customize the attribute target, assuming the name is the first argument of #state_machine
28
+ * Simplify reading from / writing to machine-related attributes on objects
29
+ * Fix locale for ActiveRecord getting added to the i18n load path multiple times [Reiner Dieterich]
30
+ * Fix callbacks, guards, and state-driven behaviors not always working on tainted classes [Brandon Dimcheff]
31
+ * Use Ruby 1.9's built-in Object#instance_exec for bound callbacks when it's available
32
+ * Improve performance of cached dynamic state lookups by 25%
33
+
34
+ == 0.7.5 / 2009-05-25
35
+
36
+ * Add built-in caching for dynamic state values when the value only needs to be generated once
37
+ * Fix flawed example for using record ids as state values
38
+ * Don't evaluate state values until they're actually used in an object instance
39
+ * Make it easier to use event attributes for actions defined in the same class as the state machine
40
+ * Fix #save/save! running transitions in ActiveRecord integrations even when a machine's action is not :save
41
+
42
+ == 0.7.4 / 2009-05-23
43
+
44
+ * Fix #save! not firing event attributes properly in ActiveRecord integrations
45
+ * Fix log files being included in gems
46
+
47
+ == 0.7.3 / 2009-04-25
48
+
49
+ * Require DataMapper version be >= 0.9.4
50
+ * Explicitly load Sequel's built-in inflector (>= 2.12.0) for scope names
51
+ * Don't use qualified name for event attributes
52
+ * Fix #valid? being defined for DataMapper resources when dm-validations isn't loaded
53
+ * Add auto-validation of values allowed for the state attribute in ORM integrations
54
+
55
+ == 0.7.2 / 2009-04-08
56
+
57
+ * Add support for running multiple methods in a callback without using blocks
58
+ * Add more flexibility around how callbacks are defined
59
+ * Add security documentation around mass-assignment in ORM integrations
60
+ * Fix event attribute transitions being publicly accessible
61
+
62
+ == 0.7.1 / 2009-04-05
63
+
64
+ * Fix machines failing to generate graphs when run from Merb tasks
65
+
66
+ == 0.7.0 / 2009-04-04
67
+
68
+ * Add #{attribute}_event for automatically firing events when the object's action is called
69
+ * Make it easier to override state-driven behaviors
70
+ * Rollback state changes when the action fails during transitions
71
+ * Use :messages instead of :invalid_message for customizing validation errors
72
+ * Use more human-readable validation errors
73
+ * Add support for more ActiveRecord observer hooks
74
+ * Add support for targeting multiple specific state machines in DataMapper observer hooks
75
+ * Don't pass the result of the action as an argument to callbacks (access via Transition#result)
76
+ * Fix incorrect results being used when running transitions in parallel
77
+ * Fix transition args not being set when run in parallel
78
+ * Allow callback terminators to be set on an application-wide basis
79
+ * Only catch :halt during before / after transition callbacks
80
+ * Fix ActiveRecord predicates being overwritten if they're already defined in the class
81
+ * Allow machine options to be set on an integration-wide basis
82
+ * Turn transactions off by default in DataMapper integrations
83
+ * Add support for configuring the use of transactions
84
+ * Simplify reading/writing of attributes
85
+ * Simplify access to state machines via #state_machine(:attribute) without generating dupes
86
+ * Fix assumptions that dm-validations is always available in DataMapper integration
87
+ * Automatically define DataMapper properties for machine attributes if they don't exist
88
+ * Add Transition#qualified_event, #qualified_from_name, and #qualified_to_name
89
+ * Add #fire_events / #fire_events! for running events on multiple state machines in parallel
90
+ * Rename next_#{event}_transition to #{event}_transition
91
+ * Add #{attribute}_transitions for getting the list of transitions that can be run on an object
92
+ * Add #{attribute}_events for getting the list of events that can be fired on an object
93
+ * Use generated non-bang event when running bang version so that overriding one affects the other
94
+ * Provide access to arguments passed into an event from transition callbacks via Transition#args
95
+
96
+ == 0.6.3 / 2009-03-10
97
+
98
+ * Add support for customizing the graph's orientation
99
+ * Use the standard visualizations for initial (open arrow) and final (double circle) states
100
+ * Highlight final states in GraphViz drawings
101
+
102
+ == 0.6.2 / 2009-03-08
103
+
104
+ * Make it easier to override generated instance / class methods
105
+
106
+ == 0.6.1 / 2009-03-07
107
+
108
+ * Add i18n support for ActiveRecord validation errors
109
+ * Add a validation error when failing to transition for ActiveRecord / DataMapper / Sequel integrations
110
+
111
+ == 0.6.0 / 2009-03-03
112
+
113
+ * Allow multiple conditions for callbacks / class behaviors
114
+ * Add support for state-driven class behavior with :if/:unless options
115
+ * Alias Machine#event as Machine#on
116
+ * Fix nil from/to states not being handled properly
117
+ * Simplify hooking callbacks into loopbacks
118
+ * Add simplified transition/callback requirement syntax
119
+
120
+ == 0.5.2 / 2009-02-17
121
+
122
+ * Improve pretty-print of events
123
+ * Simplify state/event matching design, improving guard performance by 30%
124
+ * Add better error notification when conflicting guard options are defined
125
+ * Fix scope name pluralization not being applied correctly
126
+
127
+ == 0.5.1 / 2009-02-11
128
+
129
+ * Allow states to be drawn as ellipses to accommodate long names
130
+ * Fix rake tasks not being registered in Rails/Merb applications
131
+ * Never automatically define machine attribute accessors when using an integration
132
+
133
+ == 0.5.0 / 2009-01-11
134
+
135
+ * Add to_name and from_name to transition objects
136
+ * Add nicely formatted #inspect for transitions
137
+ * Fix ActiveRecord integrations failing when the database doesn't exist yet
138
+ * Fix states not being drawn in GraphViz graphs in the correct order
139
+ * Add nicely formatted #inspect for states and events
140
+ * Simplify machine context-switching
141
+ * Store events/states in enumerable node collections
142
+ * No longer allow subclasses to change the integration
143
+ * Move fire! action logic into the Event class (no longer calls fire action on the object)
144
+ * Allow states in subclasses to have different values
145
+ * Recommend that all states be referenced as symbols instead of strings
146
+ * All states must now be named (and can be associated with other value types)
147
+ * Add support for customizing the actual stored value for a state
148
+ * Add compatibility with Ruby 1.9+
149
+
150
+ == 0.4.3 / 2008-12-28
151
+
152
+ * Allow dm-observer integration to be optional
153
+ * Fix non-lambda callbacks not working for DataMapper/Sequel
154
+
155
+ == 0.4.2 / 2008-12-28
156
+
157
+ * Fix graphs not being drawn the same way consistently
158
+ * Add support for sharing transitions across multiple events
159
+ * Add support for state-driven behavior
160
+ * Simplify initialize hooks, requiring super to be called instead
161
+ * Add :namespace option for generated state predicates / event methods
162
+
163
+ == 0.4.1 / 2008-12-16
164
+
165
+ * Fix nil states not being handled properly in guards, known states, or visualizations
166
+ * Fix the same node being used for different dynamic states in GraphViz output
167
+ * Always include initial state in the list of known states even if it's dynamic
168
+ * Use consistent naming scheme for dynamic states in GraphViz output
169
+ * Allow blocks to be directly passed into machine class
170
+ * Fix attribute predicates not working on attributes that represent columns in ActiveRecord
171
+
172
+ == 0.4.0 / 2008-12-14
173
+
174
+ * Remove the PluginAWeek namespace
175
+ * Add generic attribute predicate (e.g. "#{attribute}?(state_name)") and state predicates (e.g. "#{state}?")
176
+ * Add Sequel support
177
+ * Fix aliasing :initialize on ActiveRecord models causing warnings when the environment is reloaded
178
+ * Fix ActiveRecord state machines trying to query the database on unmigrated models
179
+ * Fix initial states not getting set when the current value is an empty string [Aaron Gibralter]
180
+ * Add rake tasks for generating graphviz files for state machines [Nate Murray]
181
+ * Fix initial state not being included in list of known states
182
+ * Add other_states directive for defining additional states not referenced in transitions or callbacks [Pete Forde]
183
+ * Add next_#{event}_transition for getting the next transition that would be performed if the event were invoked
184
+ * Add the ability to override the pluralized name of an attribute for creating scopes
185
+ * Add the ability to halt callback chains by: throw :halt
186
+ * Add support for dynamic to states in transitions (e.g. :to => lambda {Time.now})
187
+ * Add support for using real blocks in before_transition/after_transition calls instead of using the :do option
188
+ * Add DataMapper support
189
+ * Include states referenced in transition callbacks in the list of a machine's known states
190
+ * Only generate the known states for a machine on demand, rather than calculating beforehand
191
+ * Add the ability to skip state change actions during a transition (e.g. vehicle.ignite(false))
192
+ * Add the ability for the state change action (e.g. +save+ for ActiveRecord) to be configurable
193
+ * Allow state machines to be defined on *any* Ruby class, not just ActiveRecord (removes all external dependencies)
194
+ * Refactor transitions, guards, and callbacks for better organization/design
195
+ * Use a class containing the transition context in callbacks, rather than an ordered list of each individual attribute
196
+ * Add without_#{attribute} named scopes (opposite of the existing with_#{attribute} named scopes) [Sean O'Brien]
197
+
198
+ == 0.3.1 / 2008-10-26
199
+
200
+ * Fix the initial state not getting set when the state attribute is mass-assigned but protected
201
+ * Change how the base module is included to prevent namespacing conflicts
202
+
203
+ == 0.3.0 / 2008-09-07
204
+
205
+ * No longer allow additional arguments to be passed into event actions
206
+ * Add support for can_#{event}? for checking whether an event can be fired based on the current state of the record
207
+ * Don't use callbacks for performing transitions
208
+ * Fix state machines in subclasses not knowing what states/events/transitions were defined by superclasses
209
+ * Replace all before/after_exit/enter/loopback callback hooks and :before/:after options for events with before_transition/after_transition callbacks, e.g.
210
+
211
+ before_transition :from => 'parked', :do => :lock_doors # was before_exit :parked, :lock_doors
212
+ after_transition :on => 'ignite', :do => :turn_on_radio # was event :ignite, :after => :turn_on_radio do
213
+
214
+ * Always save when an event is fired even if it results in a loopback [Jürgen Strobel]
215
+ * Ensure initial state callbacks are invoked in the proper order when an event is fired on a new record
216
+ * Add before_loopback and after_loopback hooks [Jürgen Strobel]
217
+
218
+ == 0.2.1 / 2008-07-05
219
+
220
+ * Add more descriptive exceptions
221
+ * Assume the default state attribute is "state" if one is not provided
222
+ * Add :except_from option for transitions if you want to blacklist states
223
+ * Add PluginAWeek::StateMachine::Machine#states
224
+ * Add PluginAWeek::StateMachine::Event#transitions
225
+ * Allow creating transitions with no from state (effectively allowing the transition for *any* from state)
226
+ * Reduce the number of objects created for each transition
227
+
228
+ == 0.2.0 / 2008-06-29
229
+
230
+ * Add a non-bang version of events (e.g. park) that will return a boolean value for success
231
+ * Raise an exception if the bang version of events are used (e.g. park!) and no transition is successful
232
+ * Change callbacks to act a little more like ActiveRecord
233
+ * Avoid using string evaluation for dynamic methods
234
+
235
+ == 0.1.1 / 2008-06-22
236
+
237
+ * Remove log files from gems
238
+
239
+ == 0.1.0 / 2008-05-05
240
+
241
+ * Completely rewritten from scratch
242
+ * Renamed to state_machine
243
+ * Removed database dependencies
244
+ * Removed models in favor of an attribute-agnostic design
245
+ * Use ActiveSupport::Callbacks instead of eval_call
246
+ * Remove dry_transaction_rollbacks dependencies
247
+ * Added functional tests
248
+ * Updated documentation
249
+
250
+ == 0.0.1 / 2007-09-26
251
+
252
+ * Add dependency on custom_callbacks
253
+ * Move test fixtures out of the test application root directory
254
+ * Improve documentation
255
+ * Remove the StateExtension module in favor of adding singleton methods to the stateful class
256
+ * Convert dos newlines to unix newlines
257
+ * Fix error message when a given event can't be found in the database
258
+ * Add before_#{action} and #{action} callbacks when an event is performed
259
+ * All state and event callbacks can now explicitly return false in order to cancel the action
260
+ * Refactor ActiveState callback creation
261
+ * Refactor unit tests so that they use mock classes instead of themselves
262
+ * Allow force_reload option to be set in the state association
263
+ * Don't save the entire model when updating the state_id
264
+ * Raise exception if a class tries to define a state more than once
265
+ * Add tests for PluginAWeek::Has::States::ActiveState
266
+ * Refactor active state/active event creation
267
+ * Fix owner_type not being set correctly in active states/events of subclasses
268
+ * Allow subclasses to override the initial state
269
+ * Fix problem with migrations using default null when column cannot be null
270
+ * Moved deadline support into a separate plugin (has_state_deadlines).
271
+ * Added many more unit tests.
272
+ * Simplified many of the interfaces for maintainability.
273
+ * Added support for turning off recording state changes.
274
+ * Removed the short_description and long_description columns, in favor of an optional human_name column.
275
+ * Fixed not overriding the correct equality methods in the StateTransition class.
276
+ * Added to_sym to State and Event.
277
+ * State#name and Event#name now return the string version of the name instead of the symbol version.
278
+ * 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.
279
+ * Updated manual rollbacks to use the new Rails edge api (ActiveRecord::Rollback exception).
280
+ * Moved StateExtension class into a separate file in order to help keep the has_state files clean.
281
+ * Renamed InvalidState and InvalidEvent exceptions to StateNotFound and EventNotFound in order to follow the ActiveRecord convention (i.e. RecordNotFound).
282
+ * Added StateNotActive and EventNotActive exceptions to help differentiate between states which don't exist and states which weren't defined in the class.
283
+ * Added support for defining callbacks like so:
284
+
285
+ def before_exit_parked
286
+ end
287
+
288
+ def after_enter_idling
289
+ end
290
+
291
+ * Added support for defining callbacks using class methods:
292
+
293
+ before_exit_parked :fasten_seatbelt
294
+
295
+ * Added event callbacks after the transition has occurred (e.g. after_park)
296
+ * State callbacks no longer receive any of the arguments that were provided in the event action
297
+ * Updated license to include our names.
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2006-2009 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,466 @@
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://api.pluginaweek.org/state_machine
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 transition hooks with explicit transition requirements
41
+ * ActiveRecord integration
42
+ * DataMapper integration
43
+ * Sequel integration
44
+ * State predicates
45
+ * State-driven instance / class behavior
46
+ * State values of any data type
47
+ * Dynamically-generated state values
48
+ * Event parallelization
49
+ * Attribute-based event transitions
50
+ * Inheritance
51
+ * Internationalization
52
+ * GraphViz visualization creator
53
+
54
+ Examples of the usage patterns for some of the above features are shown below.
55
+ You can find much more detailed documentation in the actual API.
56
+
57
+ == Usage
58
+
59
+ === Example
60
+
61
+ Below is an example of many of the features offered by this plugin, including:
62
+ * Initial states
63
+ * Namespaced states
64
+ * Transition callbacks
65
+ * Conditional transitions
66
+ * State-driven instance behavior
67
+ * Customized state values
68
+ * Parallel events
69
+
70
+ Class definition:
71
+
72
+ class Vehicle
73
+ attr_accessor :seatbelt_on
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
+ event :park do
85
+ transition [:idling, :first_gear] => :parked
86
+ end
87
+
88
+ event :ignite do
89
+ transition :stalled => same, :parked => :idling
90
+ end
91
+
92
+ event :idle do
93
+ transition :first_gear => :idling
94
+ end
95
+
96
+ event :shift_up do
97
+ transition :idling => :first_gear, :first_gear => :second_gear, :second_gear => :third_gear
98
+ end
99
+
100
+ event :shift_down do
101
+ transition :third_gear => :second_gear, :second_gear => :first_gear
102
+ end
103
+
104
+ event :crash do
105
+ transition all - [:parked, :stalled] => :stalled, :unless => :auto_shop_busy?
106
+ end
107
+
108
+ event :repair do
109
+ transition :stalled => :parked, :if => :auto_shop_busy?
110
+ end
111
+
112
+ state :parked do
113
+ def speed
114
+ 0
115
+ end
116
+ end
117
+
118
+ state :idling, :first_gear do
119
+ def speed
120
+ 10
121
+ end
122
+ end
123
+
124
+ state :second_gear do
125
+ def speed
126
+ 20
127
+ end
128
+ end
129
+ end
130
+
131
+ state_machine :alarm_state, :initial => :active, :namespace => 'alarm' do
132
+ event :enable do
133
+ transition all => :active
134
+ end
135
+
136
+ event :disable do
137
+ transition all => :off
138
+ end
139
+
140
+ state :active, :value => 1
141
+ state :off, :value => 0
142
+ end
143
+
144
+ def initialize
145
+ @seatbelt_on = false
146
+ super() # NOTE: This *must* be called, otherwise states won't get initialized
147
+ end
148
+
149
+ def put_on_seatbelt
150
+ @seatbelt_on = true
151
+ end
152
+
153
+ def auto_shop_busy?
154
+ false
155
+ end
156
+
157
+ def tow
158
+ # tow the vehicle
159
+ end
160
+
161
+ def fix
162
+ # get the vehicle fixed by a mechanic
163
+ end
164
+ end
165
+
166
+ *Note* the comment made on the +initialize+ method in the class. In order for
167
+ state machine attributes to be properly initialized, <tt>super()</tt> must be called.
168
+ See StateMachine::MacroMethods for more information about this.
169
+
170
+ Using the above class as an example, you can interact with the state machine
171
+ like so:
172
+
173
+ vehicle = Vehicle.new # => #<Vehicle:0xb7cf4eac @state="parked", @seatbelt_on=false>
174
+ vehicle.state # => "parked"
175
+ vehicle.state_name # => :parked
176
+ vehicle.parked? # => true
177
+ vehicle.can_ignite? # => true
178
+ vehicle.ignite_transition # => #<StateMachine::Transition attribute=:state event=:ignite from="parked" from_name=:parked to="idling" to_name=:idling>
179
+ vehicle.state_events # => [:ignite]
180
+ vehicle.state_transitions # => [#<StateMachine::Transition attribute=:state event=:ignite from="parked" from_name=:parked to="idling" to_name=:idling>]
181
+ vehicle.speed # => 0
182
+
183
+ vehicle.ignite # => true
184
+ vehicle.parked? # => false
185
+ vehicle.idling? # => true
186
+ vehicle.speed # => 10
187
+ vehicle # => #<Vehicle:0xb7cf4eac @state="idling", @seatbelt_on=true>
188
+
189
+ vehicle.shift_up # => true
190
+ vehicle.speed # => 10
191
+ vehicle # => #<Vehicle:0xb7cf4eac @state="first_gear", @seatbelt_on=true>
192
+
193
+ vehicle.shift_up # => true
194
+ vehicle.speed # => 20
195
+ vehicle # => #<Vehicle:0xb7cf4eac @state="second_gear", @seatbelt_on=true>
196
+
197
+ # The bang (!) operator can raise exceptions if the event fails
198
+ vehicle.park! # => StateMachine::InvalidTransition: Cannot transition state via :park from :second_gear
199
+
200
+ # Generic state predicates can raise exceptions if the value does not exist
201
+ vehicle.state?(:parked) # => false
202
+ vehicle.state?(:invalid) # => IndexError: :invalid is an invalid name
203
+
204
+ # Namespaced machines have uniquely-generated methods
205
+ vehicle.alarm_state # => 1
206
+ vehicle.alarm_state_name # => :active
207
+
208
+ vehicle.can_disable_alarm? # => true
209
+ vehicle.disable_alarm # => true
210
+ vehicle.alarm_state # => 0
211
+ vehicle.alarm_state_name # => :off
212
+ vehicle.can_enable_alarm? # => true
213
+
214
+ vehicle.alarm_off? # => true
215
+ vehicle.alarm_active? # => false
216
+
217
+ # Events can be fired in parallel
218
+ vehicle.fire_events(:shift_down, :enable_alarm) # => true
219
+ vehicle.state_name # => :first_gear
220
+ vehicle.alarm_state_name # => :active
221
+
222
+ vehicle.fire_events!(:ignite, :enable_alarm) # => StateMachine::InvalidTransition: Cannot run events in parallel: ignite, enable_alarm
223
+
224
+ == Integrations
225
+
226
+ In addition to being able to define state machines on all Ruby classes, a set of
227
+ out-of-the-box integrations are available for some of the more popular Ruby
228
+ libraries. These integrations add library-specific behavior, allowing for state
229
+ machines to work more tightly with the conventions defined by those libraries.
230
+
231
+ The integrations currently available include:
232
+ * ActiveRecord models
233
+ * DataMapper resources
234
+ * Sequel models
235
+
236
+ A brief overview of these integrations is described below.
237
+
238
+ === ActiveRecord
239
+
240
+ The ActiveRecord integration adds support for database transactions, automatically
241
+ saving the record, named scopes, validation errors, and observers. For example,
242
+
243
+ class Vehicle < ActiveRecord::Base
244
+ state_machine :initial => :parked do
245
+ before_transition :parked => any - :parked, :do => :put_on_seatbelt
246
+ after_transition any => :parked do |vehicle, transition|
247
+ vehicle.seatbelt = 'off'
248
+ end
249
+
250
+ event :ignite do
251
+ transition :parked => :idling
252
+ end
253
+
254
+ state :first_gear, :second_gear do
255
+ validates_presence_of :seatbelt_on
256
+ end
257
+ end
258
+
259
+ def put_on_seatbelt
260
+ ...
261
+ end
262
+ end
263
+
264
+ class VehicleObserver < ActiveRecord::Observer
265
+ # Callback for :ignite event *before* the transition is performed
266
+ def before_ignite(vehicle, transition)
267
+ # log message
268
+ end
269
+
270
+ # Generic transition callback *after* the transition is performed
271
+ def after_transition(vehicle, transition)
272
+ Audit.log(vehicle, transition)
273
+ end
274
+ end
275
+
276
+ For more information about the various behaviors added for ActiveRecord state
277
+ machines, see StateMachine::Integrations::ActiveRecord.
278
+
279
+ === DataMapper
280
+
281
+ Like the ActiveRecord integration, the DataMapper integration adds support for
282
+ database transactions, automatically saving the record, named scopes, Extlib-like
283
+ callbacks, validation errors, and observers. For example,
284
+
285
+ class Vehicle
286
+ include DataMapper::Resource
287
+
288
+ property :id, Serial
289
+ property :state, String
290
+
291
+ state_machine :initial => :parked do
292
+ before_transition :parked => any - :parked, :do => :put_on_seatbelt
293
+ after_transition any => :parked do |transition|
294
+ self.seatbelt = 'off' # self is the record
295
+ end
296
+
297
+ event :ignite do
298
+ transition :parked => :idling
299
+ end
300
+
301
+ state :first_gear, :second_gear do
302
+ validates_present :seatbelt_on
303
+ end
304
+ end
305
+
306
+ def put_on_seatbelt
307
+ ...
308
+ end
309
+ end
310
+
311
+ class VehicleObserver
312
+ include DataMapper::Observer
313
+
314
+ observe Vehicle
315
+
316
+ # Callback for :ignite event *before* the transition is performed
317
+ before_transition :on => :ignite do |transition|
318
+ # log message (self is the record)
319
+ end
320
+
321
+ # Generic transition callback *after* the transition is performed
322
+ after_transition do |transition|
323
+ Audit.log(self, transition) # self is the record
324
+ end
325
+ end
326
+
327
+ *Note* that the DataMapper::Observer integration is optional and only available
328
+ when the dm-observer library is installed.
329
+
330
+ For more information about the various behaviors added for DataMapper state
331
+ machines, see StateMachine::Integrations::DataMapper.
332
+
333
+ === Sequel
334
+
335
+ Like the ActiveRecord integration, the Sequel integration adds support for
336
+ database transactions, automatically saving the record, named scopes, validation
337
+ errors and callbacks. For example,
338
+
339
+ class Vehicle < Sequel::Model
340
+ state_machine :initial => :parked do
341
+ before_transition :parked => any - :parked, :do => :put_on_seatbelt
342
+ after_transition any => :parked do |transition|
343
+ self.seatbelt = 'off' # self is the record
344
+ end
345
+
346
+ event :ignite do
347
+ transition :parked => :idling
348
+ end
349
+
350
+ state :first_gear, :second_gear do
351
+ validates_presence_of :seatbelt_on
352
+ end
353
+ end
354
+
355
+ def put_on_seatbelt
356
+ ...
357
+ end
358
+ end
359
+
360
+ For more information about the various behaviors added for Sequel state
361
+ machines, see StateMachine::Integrations::Sequel.
362
+
363
+ == Compatibility
364
+
365
+ Although state_machine introduces a simplified syntax, it still remains
366
+ backwards compatible with previous versions and other state-related libraries.
367
+ For example, transitions and callbacks can continue to be defined like so:
368
+
369
+ class Vehicle
370
+ state_machine :initial => :parked do
371
+ before_transition :from => :parked, :except_to => :parked, :do => :put_on_seatbelt
372
+ after_transition :to => :parked do |transition|
373
+ self.seatbelt = 'off' # self is the record
374
+ end
375
+
376
+ event :ignite do
377
+ transition :from => :parked, :to => :idling
378
+ end
379
+ end
380
+ end
381
+
382
+ Although this verbose syntax will most likely always be supported, it is
383
+ recommended that any state machines eventually migrate to the syntax introduced
384
+ in version 0.6.0.
385
+
386
+ == Tools
387
+
388
+ === Generating graphs
389
+
390
+ This library comes with built-in support for generating di-graphs based on the
391
+ events, states, and transitions defined for a state machine using GraphViz[http://www.graphviz.org].
392
+ This requires that both the <tt>ruby-graphviz</tt> gem and graphviz library be
393
+ installed on the system.
394
+
395
+ ==== Examples
396
+
397
+ To generate a graph for a specific file / class:
398
+
399
+ rake state_machine:draw FILE=vehicle.rb CLASS=Vehicle
400
+
401
+ To save files to a specific path:
402
+
403
+ rake state_machine:draw FILE=vehicle.rb CLASS=Vehicle TARGET=files
404
+
405
+ To customize the image format / orientation:
406
+
407
+ rake state_machine:draw FILE=vehicle.rb CLASS=Vehicle FORMAT=jpg ORIENTATION=landscape
408
+
409
+ To generate multiple state machine graphs:
410
+
411
+ rake state_machine:draw FILE=vehicle.rb,car.rb CLASS=Vehicle,Car
412
+
413
+ *Note* that this will generate a different file for every state machine defined
414
+ in the class. The generated files will use an output filename of the format
415
+ #{class_name}_#{machine_name}.#{format}.
416
+
417
+ For examples of actual images generated using this task, see those under the
418
+ examples folder.
419
+
420
+ ==== Ruby on Rails Integration
421
+
422
+ There is a special integration Rake task for generating state machines for
423
+ classes used in a Ruby on Rails application. This task will load the application
424
+ environment, meaning that it's unnecessary to specify the actual file to load.
425
+
426
+ For example,
427
+
428
+ rake state_machine:draw:rails CLASS=Vehicle
429
+
430
+ ==== Merb Integration
431
+
432
+ Like Ruby on Rails, there is a special integration Rake task for generating
433
+ state machines for classes used in a Merb application. This task will load the
434
+ application environment, meaning that it's unnecessary to specify the actual
435
+ files to load.
436
+
437
+ For example,
438
+
439
+ rake state_machine:draw:merb CLASS=Vehicle
440
+
441
+ === Interactive graphs
442
+
443
+ Jean Bovet - {Visual Automata Simulator}[http://www.cs.usfca.edu/~jbovet/vas.html].
444
+ This is a great tool for "simulating, visualizing and transforming finite state
445
+ automata and Turing Machines". This tool can help in the creation of states and
446
+ events for your models. It is cross-platform, written in Java.
447
+
448
+ == Testing
449
+
450
+ To run the entire test suite (will test ActiveRecord, DataMapper, and Sequel
451
+ integrations if the proper dependencies are available):
452
+
453
+ rake test
454
+
455
+ Target specific versions of integrations like so:
456
+
457
+ rake test AR_VERSION=2.1.0 DM_VERSION=0.9.4 SEQUEL_VERSION=2.8.0
458
+
459
+ == Dependencies
460
+
461
+ By default, there are no dependencies. If using specific integrations, those
462
+ dependencies are listed below.
463
+
464
+ * ActiveRecord[http://rubyonrails.org] integration: 2.1.0 or later
465
+ * DataMapper[http://datamapper.org] integration: 0.9.4 or later
466
+ * Sequel[http://sequel.rubyforge.org] integration: 2.8.0 or later