pluginaweek-state_machine 0.7.6

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