mattscilipoti-state_machine 0.8.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (78) hide show
  1. data/CHANGELOG.rdoc +298 -0
  2. data/LICENSE +20 -0
  3. data/README.rdoc +474 -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/tasks.rb +30 -0
  51. data/lib/state_machine/transition.rb +394 -0
  52. data/tasks/state_machine.rake +1 -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 +1367 -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 +155 -0
data/CHANGELOG.rdoc ADDED
@@ -0,0 +1,298 @@
1
+ == master
2
+
3
+ * Add support for ActiveRecord 2.0.*
4
+ * Fix nil states being overwritten when they're explicitly set in ORM integrations
5
+ * Fix default states not getting set in ORM integrations if the column has a default
6
+ * Fix event transitions being kept around while running actions/callbacks, sometimes preventing object marshalling
7
+
8
+ == 0.8.0 / 2009-08-15
9
+
10
+ * Add support for DataMapper 0.10.0
11
+ * Always interpet nil return values from actions as failed attempts
12
+ * Fix loopbacks not causing records to save in ORM integrations if no other fields were changed
13
+ * Fix events not failing with useful errors when an object's state is invalid
14
+ * Use more friendly NoMethodError messages for state-driven behaviors
15
+ * Fix before_transition callbacks getting run twice when using event attributes in ORM integrations
16
+ * Add the ability to query for the availability of specific transitions on an object
17
+ * Allow after_transition callbacks to be explicitly run on failed attempts
18
+ * By default, don't run after_transition callbacks on failed attempts
19
+ * Fix not allowing multiple methods to be specified as arguments in callbacks
20
+ * Fix initial states being set when loading records from the database in Sequel integration
21
+ * Allow static initial states to be set earlier in the initialization of an object
22
+ * Use friendly validation errors for nil states
23
+ * Fix states not being validated properly when using custom names in ActiveRecord / DataMapper integrations
24
+
25
+ == 0.7.6 / 2009-06-17
26
+
27
+ * Allow multiple state machines on the same class to target the same attribute
28
+ * Add support for :attribute to customize the attribute target, assuming the name is the first argument of #state_machine
29
+ * Simplify reading from / writing to machine-related attributes on objects
30
+ * Fix locale for ActiveRecord getting added to the i18n load path multiple times [Reiner Dieterich]
31
+ * Fix callbacks, guards, and state-driven behaviors not always working on tainted classes [Brandon Dimcheff]
32
+ * Use Ruby 1.9's built-in Object#instance_exec for bound callbacks when it's available
33
+ * Improve performance of cached dynamic state lookups by 25%
34
+
35
+ == 0.7.5 / 2009-05-25
36
+
37
+ * Add built-in caching for dynamic state values when the value only needs to be generated once
38
+ * Fix flawed example for using record ids as state values
39
+ * Don't evaluate state values until they're actually used in an object instance
40
+ * Make it easier to use event attributes for actions defined in the same class as the state machine
41
+ * Fix #save/save! running transitions in ActiveRecord integrations even when a machine's action is not :save
42
+
43
+ == 0.7.4 / 2009-05-23
44
+
45
+ * Fix #save! not firing event attributes properly in ActiveRecord integrations
46
+ * Fix log files being included in gems
47
+
48
+ == 0.7.3 / 2009-04-25
49
+
50
+ * Require DataMapper version be >= 0.9.4
51
+ * Explicitly load Sequel's built-in inflector (>= 2.12.0) for scope names
52
+ * Don't use qualified name for event attributes
53
+ * Fix #valid? being defined for DataMapper resources when dm-validations isn't loaded
54
+ * Add auto-validation of values allowed for the state attribute in ORM integrations
55
+
56
+ == 0.7.2 / 2009-04-08
57
+
58
+ * Add support for running multiple methods in a callback without using blocks
59
+ * Add more flexibility around how callbacks are defined
60
+ * Add security documentation around mass-assignment in ORM integrations
61
+ * Fix event attribute transitions being publicly accessible
62
+
63
+ == 0.7.1 / 2009-04-05
64
+
65
+ * Fix machines failing to generate graphs when run from Merb tasks
66
+
67
+ == 0.7.0 / 2009-04-04
68
+
69
+ * Add #{attribute}_event for automatically firing events when the object's action is called
70
+ * Make it easier to override state-driven behaviors
71
+ * Rollback state changes when the action fails during transitions
72
+ * Use :messages instead of :invalid_message for customizing validation errors
73
+ * Use more human-readable validation errors
74
+ * Add support for more ActiveRecord observer hooks
75
+ * Add support for targeting multiple specific state machines in DataMapper observer hooks
76
+ * Don't pass the result of the action as an argument to callbacks (access via Transition#result)
77
+ * Fix incorrect results being used when running transitions in parallel
78
+ * Fix transition args not being set when run in parallel
79
+ * Allow callback terminators to be set on an application-wide basis
80
+ * Only catch :halt during before / after transition callbacks
81
+ * Fix ActiveRecord predicates being overwritten if they're already defined in the class
82
+ * Allow machine options to be set on an integration-wide basis
83
+ * Turn transactions off by default in DataMapper integrations
84
+ * Add support for configuring the use of transactions
85
+ * Simplify reading/writing of attributes
86
+ * Simplify access to state machines via #state_machine(:attribute) without generating dupes
87
+ * Fix assumptions that dm-validations is always available in DataMapper integration
88
+ * Automatically define DataMapper properties for machine attributes if they don't exist
89
+ * Add Transition#qualified_event, #qualified_from_name, and #qualified_to_name
90
+ * Add #fire_events / #fire_events! for running events on multiple state machines in parallel
91
+ * Rename next_#{event}_transition to #{event}_transition
92
+ * Add #{attribute}_transitions for getting the list of transitions that can be run on an object
93
+ * Add #{attribute}_events for getting the list of events that can be fired on an object
94
+ * Use generated non-bang event when running bang version so that overriding one affects the other
95
+ * Provide access to arguments passed into an event from transition callbacks via Transition#args
96
+
97
+ == 0.6.3 / 2009-03-10
98
+
99
+ * Add support for customizing the graph's orientation
100
+ * Use the standard visualizations for initial (open arrow) and final (double circle) states
101
+ * Highlight final states in GraphViz drawings
102
+
103
+ == 0.6.2 / 2009-03-08
104
+
105
+ * Make it easier to override generated instance / class methods
106
+
107
+ == 0.6.1 / 2009-03-07
108
+
109
+ * Add i18n support for ActiveRecord validation errors
110
+ * Add a validation error when failing to transition for ActiveRecord / DataMapper / Sequel integrations
111
+
112
+ == 0.6.0 / 2009-03-03
113
+
114
+ * Allow multiple conditions for callbacks / class behaviors
115
+ * Add support for state-driven class behavior with :if/:unless options
116
+ * Alias Machine#event as Machine#on
117
+ * Fix nil from/to states not being handled properly
118
+ * Simplify hooking callbacks into loopbacks
119
+ * Add simplified transition/callback requirement syntax
120
+
121
+ == 0.5.2 / 2009-02-17
122
+
123
+ * Improve pretty-print of events
124
+ * Simplify state/event matching design, improving guard performance by 30%
125
+ * Add better error notification when conflicting guard options are defined
126
+ * Fix scope name pluralization not being applied correctly
127
+
128
+ == 0.5.1 / 2009-02-11
129
+
130
+ * Allow states to be drawn as ellipses to accommodate long names
131
+ * Fix rake tasks not being registered in Rails/Merb applications
132
+ * Never automatically define machine attribute accessors when using an integration
133
+
134
+ == 0.5.0 / 2009-01-11
135
+
136
+ * Add to_name and from_name to transition objects
137
+ * Add nicely formatted #inspect for transitions
138
+ * Fix ActiveRecord integrations failing when the database doesn't exist yet
139
+ * Fix states not being drawn in GraphViz graphs in the correct order
140
+ * Add nicely formatted #inspect for states and events
141
+ * Simplify machine context-switching
142
+ * Store events/states in enumerable node collections
143
+ * No longer allow subclasses to change the integration
144
+ * Move fire! action logic into the Event class (no longer calls fire action on the object)
145
+ * Allow states in subclasses to have different values
146
+ * Recommend that all states be referenced as symbols instead of strings
147
+ * All states must now be named (and can be associated with other value types)
148
+ * Add support for customizing the actual stored value for a state
149
+ * Add compatibility with Ruby 1.9+
150
+
151
+ == 0.4.3 / 2008-12-28
152
+
153
+ * Allow dm-observer integration to be optional
154
+ * Fix non-lambda callbacks not working for DataMapper/Sequel
155
+
156
+ == 0.4.2 / 2008-12-28
157
+
158
+ * Fix graphs not being drawn the same way consistently
159
+ * Add support for sharing transitions across multiple events
160
+ * Add support for state-driven behavior
161
+ * Simplify initialize hooks, requiring super to be called instead
162
+ * Add :namespace option for generated state predicates / event methods
163
+
164
+ == 0.4.1 / 2008-12-16
165
+
166
+ * Fix nil states not being handled properly in guards, known states, or visualizations
167
+ * Fix the same node being used for different dynamic states in GraphViz output
168
+ * Always include initial state in the list of known states even if it's dynamic
169
+ * Use consistent naming scheme for dynamic states in GraphViz output
170
+ * Allow blocks to be directly passed into machine class
171
+ * Fix attribute predicates not working on attributes that represent columns in ActiveRecord
172
+
173
+ == 0.4.0 / 2008-12-14
174
+
175
+ * Remove the PluginAWeek namespace
176
+ * Add generic attribute predicate (e.g. "#{attribute}?(state_name)") and state predicates (e.g. "#{state}?")
177
+ * Add Sequel support
178
+ * Fix aliasing :initialize on ActiveRecord models causing warnings when the environment is reloaded
179
+ * Fix ActiveRecord state machines trying to query the database on unmigrated models
180
+ * Fix initial states not getting set when the current value is an empty string [Aaron Gibralter]
181
+ * Add rake tasks for generating graphviz files for state machines [Nate Murray]
182
+ * Fix initial state not being included in list of known states
183
+ * Add other_states directive for defining additional states not referenced in transitions or callbacks [Pete Forde]
184
+ * Add next_#{event}_transition for getting the next transition that would be performed if the event were invoked
185
+ * Add the ability to override the pluralized name of an attribute for creating scopes
186
+ * Add the ability to halt callback chains by: throw :halt
187
+ * Add support for dynamic to states in transitions (e.g. :to => lambda {Time.now})
188
+ * Add support for using real blocks in before_transition/after_transition calls instead of using the :do option
189
+ * Add DataMapper support
190
+ * Include states referenced in transition callbacks in the list of a machine's known states
191
+ * Only generate the known states for a machine on demand, rather than calculating beforehand
192
+ * Add the ability to skip state change actions during a transition (e.g. vehicle.ignite(false))
193
+ * Add the ability for the state change action (e.g. +save+ for ActiveRecord) to be configurable
194
+ * Allow state machines to be defined on *any* Ruby class, not just ActiveRecord (removes all external dependencies)
195
+ * Refactor transitions, guards, and callbacks for better organization/design
196
+ * Use a class containing the transition context in callbacks, rather than an ordered list of each individual attribute
197
+ * Add without_#{attribute} named scopes (opposite of the existing with_#{attribute} named scopes) [Sean O'Brien]
198
+
199
+ == 0.3.1 / 2008-10-26
200
+
201
+ * Fix the initial state not getting set when the state attribute is mass-assigned but protected
202
+ * Change how the base module is included to prevent namespacing conflicts
203
+
204
+ == 0.3.0 / 2008-09-07
205
+
206
+ * No longer allow additional arguments to be passed into event actions
207
+ * Add support for can_#{event}? for checking whether an event can be fired based on the current state of the record
208
+ * Don't use callbacks for performing transitions
209
+ * Fix state machines in subclasses not knowing what states/events/transitions were defined by superclasses
210
+ * Replace all before/after_exit/enter/loopback callback hooks and :before/:after options for events with before_transition/after_transition callbacks, e.g.
211
+
212
+ before_transition :from => 'parked', :do => :lock_doors # was before_exit :parked, :lock_doors
213
+ after_transition :on => 'ignite', :do => :turn_on_radio # was event :ignite, :after => :turn_on_radio do
214
+
215
+ * Always save when an event is fired even if it results in a loopback [Jürgen Strobel]
216
+ * Ensure initial state callbacks are invoked in the proper order when an event is fired on a new record
217
+ * Add before_loopback and after_loopback hooks [Jürgen Strobel]
218
+
219
+ == 0.2.1 / 2008-07-05
220
+
221
+ * Add more descriptive exceptions
222
+ * Assume the default state attribute is "state" if one is not provided
223
+ * Add :except_from option for transitions if you want to blacklist states
224
+ * Add PluginAWeek::StateMachine::Machine#states
225
+ * Add PluginAWeek::StateMachine::Event#transitions
226
+ * Allow creating transitions with no from state (effectively allowing the transition for *any* from state)
227
+ * Reduce the number of objects created for each transition
228
+
229
+ == 0.2.0 / 2008-06-29
230
+
231
+ * Add a non-bang version of events (e.g. park) that will return a boolean value for success
232
+ * Raise an exception if the bang version of events are used (e.g. park!) and no transition is successful
233
+ * Change callbacks to act a little more like ActiveRecord
234
+ * Avoid using string evaluation for dynamic methods
235
+
236
+ == 0.1.1 / 2008-06-22
237
+
238
+ * Remove log files from gems
239
+
240
+ == 0.1.0 / 2008-05-05
241
+
242
+ * Completely rewritten from scratch
243
+ * Renamed to state_machine
244
+ * Removed database dependencies
245
+ * Removed models in favor of an attribute-agnostic design
246
+ * Use ActiveSupport::Callbacks instead of eval_call
247
+ * Remove dry_transaction_rollbacks dependencies
248
+ * Added functional tests
249
+ * Updated documentation
250
+
251
+ == 0.0.1 / 2007-09-26
252
+
253
+ * Add dependency on custom_callbacks
254
+ * Move test fixtures out of the test application root directory
255
+ * Improve documentation
256
+ * Remove the StateExtension module in favor of adding singleton methods to the stateful class
257
+ * Convert dos newlines to unix newlines
258
+ * Fix error message when a given event can't be found in the database
259
+ * Add before_#{action} and #{action} callbacks when an event is performed
260
+ * All state and event callbacks can now explicitly return false in order to cancel the action
261
+ * Refactor ActiveState callback creation
262
+ * Refactor unit tests so that they use mock classes instead of themselves
263
+ * Allow force_reload option to be set in the state association
264
+ * Don't save the entire model when updating the state_id
265
+ * Raise exception if a class tries to define a state more than once
266
+ * Add tests for PluginAWeek::Has::States::ActiveState
267
+ * Refactor active state/active event creation
268
+ * Fix owner_type not being set correctly in active states/events of subclasses
269
+ * Allow subclasses to override the initial state
270
+ * Fix problem with migrations using default null when column cannot be null
271
+ * Moved deadline support into a separate plugin (has_state_deadlines).
272
+ * Added many more unit tests.
273
+ * Simplified many of the interfaces for maintainability.
274
+ * Added support for turning off recording state changes.
275
+ * Removed the short_description and long_description columns, in favor of an optional human_name column.
276
+ * Fixed not overriding the correct equality methods in the StateTransition class.
277
+ * Added to_sym to State and Event.
278
+ * State#name and Event#name now return the string version of the name instead of the symbol version.
279
+ * 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.
280
+ * Updated manual rollbacks to use the new Rails edge api (ActiveRecord::Rollback exception).
281
+ * Moved StateExtension class into a separate file in order to help keep the has_state files clean.
282
+ * Renamed InvalidState and InvalidEvent exceptions to StateNotFound and EventNotFound in order to follow the ActiveRecord convention (i.e. RecordNotFound).
283
+ * Added StateNotActive and EventNotActive exceptions to help differentiate between states which don't exist and states which weren't defined in the class.
284
+ * Added support for defining callbacks like so:
285
+
286
+ def before_exit_parked
287
+ end
288
+
289
+ def after_enter_idling
290
+ end
291
+
292
+ * Added support for defining callbacks using class methods:
293
+
294
+ before_exit_parked :fasten_seatbelt
295
+
296
+ * Added event callbacks after the transition has occurred (e.g. after_park)
297
+ * State callbacks no longer receive any of the arguments that were provided in the event action
298
+ * 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,474 @@
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
+ Using as a gem? Add this to your Rakefile:
431
+
432
+ begin
433
+ require 'state_machine/tasks'
434
+ rescue LoadError
435
+ STDERR.puts "Run `rake gems:install` to install state_machine"
436
+ end
437
+
438
+ ==== Merb Integration
439
+
440
+ Like Ruby on Rails, there is a special integration Rake task for generating
441
+ state machines for classes used in a Merb application. This task will load the
442
+ application environment, meaning that it's unnecessary to specify the actual
443
+ files to load.
444
+
445
+ For example,
446
+
447
+ rake state_machine:draw:merb CLASS=Vehicle
448
+
449
+ === Interactive graphs
450
+
451
+ Jean Bovet - {Visual Automata Simulator}[http://www.cs.usfca.edu/~jbovet/vas.html].
452
+ This is a great tool for "simulating, visualizing and transforming finite state
453
+ automata and Turing Machines". This tool can help in the creation of states and
454
+ events for your models. It is cross-platform, written in Java.
455
+
456
+ == Testing
457
+
458
+ To run the entire test suite (will test ActiveRecord, DataMapper, and Sequel
459
+ integrations if the proper dependencies are available):
460
+
461
+ rake test
462
+
463
+ Target specific versions of integrations like so:
464
+
465
+ rake test AR_VERSION=2.0.0 DM_VERSION=0.9.4 SEQUEL_VERSION=2.8.0
466
+
467
+ == Dependencies
468
+
469
+ By default, there are no dependencies. If using specific integrations, those
470
+ dependencies are listed below.
471
+
472
+ * ActiveRecord[http://rubyonrails.org] integration: 2.0.0 or later
473
+ * DataMapper[http://datamapper.org] integration: 0.9.4 or later
474
+ * Sequel[http://sequel.rubyforge.org] integration: 2.8.0 or later