public
Description: Adds support for creating state machines for attributes on any Ruby class
Homepage: http://www.pluginaweek.org
Clone URL: git://github.com/pluginaweek/state_machine.git
obrie (author)
Tue Dec 16 10:44:11 -0800 2008
commit  2035234983bb00c605e32c2b7905e5e2c95bb0c5
tree    f4c65fa69685d4dd35c2a705c16649baab2bb1aa
parent  d8c54f620b43eb4acd6122906b035b29e167e5ce
state_machine / README.rdoc
f3565041 » obrie 2008-05-04 Completely rewritten from s... 1 == state_machine
03ead942 » obrie 2006-10-21 Initial import. 2
307ac8eb » obrie 2008-12-07 Re-design to be library-agn... 3 +state_machine+ adds support for creating state machines for attributes on any
4 Ruby class.
03ead942 » obrie 2006-10-21 Initial import. 5
29c1d69f » obrie 2007-01-01 Removed install.rb files. 6 == Resources
7
1c257492 » obrie 2007-09-26 Update README format 8 API
29c1d69f » obrie 2007-01-01 Removed install.rb files. 9
f3565041 » obrie 2008-05-04 Completely rewritten from s... 10 * http://api.pluginaweek.org/state_machine
29c1d69f » obrie 2007-01-01 Removed install.rb files. 11
695fcf70 » obrie 2008-06-25 Update list of plugin resou... 12 Bugs
13
14 * http://pluginaweek.lighthouseapp.com/projects/13288-state_machine
15
29c1d69f » obrie 2007-01-01 Removed install.rb files. 16 Development
17
695fcf70 » obrie 2008-06-25 Update list of plugin resou... 18 * http://github.com/pluginaweek/state_machine
29c1d69f » obrie 2007-01-01 Removed install.rb files. 19
1c257492 » obrie 2007-09-26 Update README format 20 Source
21
695fcf70 » obrie 2008-06-25 Update list of plugin resou... 22 * git://github.com/pluginaweek/state_machine.git
1c257492 » obrie 2007-09-26 Update README format 23
29c1d69f » obrie 2007-01-01 Removed install.rb files. 24 == Description
25
307ac8eb » obrie 2008-12-07 Re-design to be library-agn... 26 State machines make it dead-simple to manage the behavior of a class. Too often,
27 the status 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.
29c1d69f » obrie 2007-01-01 Removed install.rb files. 30
179ce54f » obrie 2008-07-05 Add more descriptive except... 31 +state_machine+ simplifies this design by introducing the various parts of a real
b68d4bda » obrie 2008-12-14 Add Sequel support 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 * before/after transition hooks with explicit transition requirements
40 * ActiveRecord integration
41 * DataMapper integration
42 * Sequel integration
43 * States of any data type
48d83dfb » obrie 2008-12-14 Add generic attribute predi... Comment 44 * State predicates
b68d4bda » obrie 2008-12-14 Add Sequel support 45 * GraphViz visualization creator
46
47 Examples of the usage patterns for some of the above features are shown below.
48 You can find more detailed documentation in the actual API.
8563ca6b » obrie 2007-09-21 More documentation 49
50 == Usage
29c1d69f » obrie 2007-01-01 Removed install.rb files. 51
f3565041 » obrie 2008-05-04 Completely rewritten from s... 52 === Example
53
179ce54f » obrie 2008-07-05 Add more descriptive except... 54 Below is an example of many of the features offered by this plugin, including
55 * Initial states
6a94afcc » obrie 2008-09-06 MAJOR REWRITE! Replace all ... Comment 56 * Transition callbacks
179ce54f » obrie 2008-07-05 Add more descriptive except... 57 * Conditional transitions
58
307ac8eb » obrie 2008-12-07 Re-design to be library-agn... 59 class Vehicle
60 attr_accessor :seatbelt_on
61
c02e3dc0 » obrie 2008-10-04 Fix state machine examples ... 62 state_machine :state, :initial => 'parked' do
6a94afcc » obrie 2008-09-06 MAJOR REWRITE! Replace all ... Comment 63 before_transition :from => %w(parked idling), :do => :put_on_seatbelt
307ac8eb » obrie 2008-12-07 Re-design to be library-agn... 64 after_transition :on => 'crash', :do => :tow
65 after_transition :on => 'repair', :do => :fix
66 after_transition :to => 'parked' do |vehicle, transition|
67 vehicle.seatbelt_on = false
68 end
f3565041 » obrie 2008-05-04 Completely rewritten from s... 69
70 event :park do
71 transition :to => 'parked', :from => %w(idling first_gear)
72 end
73
74 event :ignite do
75 transition :to => 'stalled', :from => 'stalled'
76 transition :to => 'idling', :from => 'parked'
77 end
78
79 event :idle do
80 transition :to => 'idling', :from => 'first_gear'
81 end
82
83 event :shift_up do
84 transition :to => 'first_gear', :from => 'idling'
85 transition :to => 'second_gear', :from => 'first_gear'
86 transition :to => 'third_gear', :from => 'second_gear'
87 end
88
89 event :shift_down do
90 transition :to => 'second_gear', :from => 'third_gear'
91 transition :to => 'first_gear', :from => 'second_gear'
92 end
93
6a94afcc » obrie 2008-09-06 MAJOR REWRITE! Replace all ... Comment 94 event :crash do
f3565041 » obrie 2008-05-04 Completely rewritten from s... 95 transition :to => 'stalled', :from => %w(first_gear second_gear third_gear), :unless => :auto_shop_busy?
96 end
97
6a94afcc » obrie 2008-09-06 MAJOR REWRITE! Replace all ... Comment 98 event :repair do
f3565041 » obrie 2008-05-04 Completely rewritten from s... 99 transition :to => 'parked', :from => 'stalled', :if => :auto_shop_busy?
100 end
101 end
179ce54f » obrie 2008-07-05 Add more descriptive except... 102
307ac8eb » obrie 2008-12-07 Re-design to be library-agn... 103 def initialize
104 @seatbelt_on = false
179ce54f » obrie 2008-07-05 Add more descriptive except... 105 end
106
307ac8eb » obrie 2008-12-07 Re-design to be library-agn... 107 def put_on_seatbelt
108 @seatbelt_on = true
179ce54f » obrie 2008-07-05 Add more descriptive except... 109 end
110
111 def auto_shop_busy?
112 false
113 end
307ac8eb » obrie 2008-12-07 Re-design to be library-agn... 114
115 def tow
116 # tow the vehicle
117 end
118
119 def fix
120 # get the vehicle fixed by a mechanic
121 end
f3565041 » obrie 2008-05-04 Completely rewritten from s... 122 end
b5066674 » obrie 2007-09-21 Move test fixtures out of t... 123
307ac8eb » obrie 2008-12-07 Re-design to be library-agn... 124 Using the above class as an example, you can interact with the state machine
179ce54f » obrie 2008-07-05 Add more descriptive except... 125 like so:
126
b68d4bda » obrie 2008-12-14 Add Sequel support 127 vehicle = Vehicle.new # => #<Vehicle:0xb7cf4eac @state="parked", @seatbelt_on=false>
48d83dfb » obrie 2008-12-14 Add generic attribute predi... Comment 128 vehicle.parked? # => true
b68d4bda » obrie 2008-12-14 Add Sequel support 129 vehicle.can_ignite? # => true
146afff4 » obrie 2008-12-14 Remove the PluginAWeek name... 130 vehicle.next_ignite_transition # => #<StateMachine::Transition:0xb7c34cec ...>
b68d4bda » obrie 2008-12-14 Add Sequel support 131 vehicle.ignite # => true
48d83dfb » obrie 2008-12-14 Add generic attribute predi... Comment 132 vehicle.parked? # => false
133 vehicle.idling? # => true
b68d4bda » obrie 2008-12-14 Add Sequel support 134 vehicle # => #<Vehicle:0xb7cf4eac @state="idling", @seatbelt_on=true>
135 vehicle.shift_up # => true
136 vehicle # => #<Vehicle:0xb7cf4eac @state="first_gear", @seatbelt_on=true>
137 vehicle.shift_up # => true
138 vehicle # => #<Vehicle:0xb7cf4eac @state="second_gear", @seatbelt_on=true>
179ce54f » obrie 2008-07-05 Add more descriptive except... 139
140 # The bang (!) operator can raise exceptions if the event fails
146afff4 » obrie 2008-12-14 Remove the PluginAWeek name... 141 vehicle.park! # => StateMachine::InvalidTransition: Cannot transition via :park from "second_gear"
48d83dfb » obrie 2008-12-14 Add generic attribute predi... Comment 142
143 # Generic state predicates can raise exceptions if the value does not exist
144 vehicle.state?('parked') # => true
145 vehicle.state?('invalid') # => ArgumentError: "parked" is not a known state value
6a94afcc » obrie 2008-09-06 MAJOR REWRITE! Replace all ... Comment 146
307ac8eb » obrie 2008-12-07 Re-design to be library-agn... 147 == Integrations
148
149 In addition to being able to define state machines on all Ruby classes, a set of
150 out-of-the-box integrations are available for some of the more popular Ruby
151 libraries. These integrations add library-specific behavior, allowing for state
152 machines to work more tightly with the conventions defined by those libraries.
153
154 The integrations currently available include:
155 * ActiveRecord models
156 * DataMapper resources
b68d4bda » obrie 2008-12-14 Add Sequel support 157 * Sequel models
307ac8eb » obrie 2008-12-07 Re-design to be library-agn... 158
159 A brief overview of these integrations is described below.
160
161 === ActiveRecord
162
163 The ActiveRecord integration adds support for database transactions, automatically
164 saving the record, named scopes, and observers. For example,
165
166 class Vehicle < ActiveRecord::Base
167 state_machine :initial => 'parked' do
168 before_transition :to => 'idling', :do => :put_on_seatbelt
169 after_transition :to => 'parked' do |vehicle, transition|
170 vehicle.seatbelt = 'off'
171 end
172
173 event :ignite do
174 transition :to => 'idling', :from => 'parked'
175 end
176 end
177
178 def put_on_seatbelt
179 ...
180 end
181 end
182
183 class VehicleObserver < ActiveRecord::Observer
184 # Callback for :ignite event *before* the transition is performed
185 def before_ignite(vehicle, transition)
186 # log message
187 end
188
189 # Generic transition callback *before* the transition is performed
190 def after_transition(vehicle, transition)
191 Audit.log(vehicle, transition)
192 end
193 end
194
195 For more information about the various behaviors added for ActiveRecord state
146afff4 » obrie 2008-12-14 Remove the PluginAWeek name... 196 machines, see StateMachine::Integrations::ActiveRecord.
307ac8eb » obrie 2008-12-07 Re-design to be library-agn... 197
198 ==== With enumerations
6a94afcc » obrie 2008-09-06 MAJOR REWRITE! Replace all ... Comment 199
200 Using the acts_as_enumeration[http://github.com/pluginaweek/acts_as_enumeration] plugin
307ac8eb » obrie 2008-12-07 Re-design to be library-agn... 201 with an ActiveRecord integration, states can be transparently stored using
202 record ids in the database like so:
6a94afcc » obrie 2008-09-06 MAJOR REWRITE! Replace all ... Comment 203
204 class VehicleState < ActiveRecord::Base
205 acts_as_enumeration
206
207 create :id => 1, :name => 'parked'
208 create :id => 2, :name => 'idling'
209 ...
210 end
211
212 class Vehicle < ActiveRecord::Base
213 belongs_to :state, :class_name => 'VehicleState'
214
c02e3dc0 » obrie 2008-10-04 Fix state machine examples ... 215 state_machine :state, :initial => 'parked' do
6a94afcc » obrie 2008-09-06 MAJOR REWRITE! Replace all ... Comment 216 ...
217
218 event :park do
219 transition :to => 'parked', :from => %w(idling first_gear)
220 end
221 end
222
223 ...
224 end
225
307ac8eb » obrie 2008-12-07 Re-design to be library-agn... 226 Notice that the state machine definition remains *exactly* the same. However,
227 when interacting with the records, the actual state will be stored using the
228 identifiers defined for the enumeration:
6a94afcc » obrie 2008-09-06 MAJOR REWRITE! Replace all ... Comment 229
230 vehicle = Vehicle.create # => #<Vehicle id: 1, seatbelt_on: false, state_id: 1>
231 vehicle.ignite # => true
232 vehicle # => #<Vehicle id: 1, seatbelt_on: true, state_id: 2>
233
234 This allows states to take on more complex functionality other than just being
235 a string value.
179ce54f » obrie 2008-07-05 Add more descriptive except... 236
307ac8eb » obrie 2008-12-07 Re-design to be library-agn... 237 === DataMapper
238
239 Like the ActiveRecord integration, the DataMapper integration adds support for
240 database transactions, automatically saving the record, named scopes, Extlib-like
241 callbacks, and observers. For example,
242
243 class Vehicle
244 include DataMapper::Resource
245
246 property :id, Serial
247 property :state, String
248
249 state_machine :initial => 'parked' do
250 before_transition :to => 'idling', :do => :put_on_seatbelt
251 after_transition :to => 'parked' do |transition|
252 self.seatbelt = 'off' # self is the record
253 end
254
255 event :ignite do
256 transition :to => 'idling', :from => 'parked'
257 end
258 end
259
260 def put_on_seatbelt
261 ...
262 end
263 end
264
265 class VehicleObserver
266 include DataMapper::Observer
267
268 observe Vehicle
269
270 # Callback for :ignite event *before* the transition is performed
271 before_transition :on => :ignite do |transition|
272 # log message (self is the record)
273 end
274
275 # Generic transition callback *before* the transition is performed
276 after_transition do |transition, saved|
277 Audit.log(self, transition) if saved # self is the record
278 end
279 end
280
281 For more information about the various behaviors added for DataMapper state
146afff4 » obrie 2008-12-14 Remove the PluginAWeek name... 282 machines, see StateMachine::Integrations::DataMapper.
307ac8eb » obrie 2008-12-07 Re-design to be library-agn... 283
b68d4bda » obrie 2008-12-14 Add Sequel support 284 === Sequel
285
286 Like the ActiveRecord integration, the Sequel integration adds support for
287 database transactions, automatically saving the record, named scopes, and
288 callbacks. For example,
289
290 class Vehicle < Sequel::Model
291 state_machine :initial => 'parked' do
292 before_transition :to => 'idling', :do => :put_on_seatbelt
293 after_transition :to => 'parked' do |transition|
294 self.seatbelt = 'off' # self is the record
295 end
296
297 event :ignite do
298 transition :to => 'idling', :from => 'parked'
299 end
300 end
301
302 def put_on_seatbelt
303 ...
304 end
305 end
306
307 For more information about the various behaviors added for Sequel state
146afff4 » obrie 2008-12-14 Remove the PluginAWeek name... 308 machines, see StateMachine::Integrations::Sequel.
b68d4bda » obrie 2008-12-14 Add Sequel support 309
b5066674 » obrie 2007-09-21 Move test fixtures out of t... 310 == Tools
311
6f25124e » jashmenn 2008-12-08 Add rake tasks for generati... Comment 312 === Generating graphs
313
314 This library comes with built-in support for generating di-graphs based on the
315 events, states, and transitions defined for a state machine using GraphViz[http://www.graphviz.org].
316 This requires that both the <tt>ruby-graphviz</tt> gem and graphviz library be
317 installed on the system.
318
319 ==== Examples
320
321 To generate a graph for a specific file / class:
322
323 rake state_machine:draw FILE=vehicle.rb CLASS=Vehicle
324
325 To save files to a specific path:
326
327 rake state_machine:draw FILE=vehicle.rb CLASS=Vehicle TARGET=files
328
329 To customize the image format:
330
331 rake state_machine:draw FILE=vehicle.rb CLASS=Vehicle FORMAT=jpg
332
333 To generate multiple state machine graphs:
334
335 rake state_machine:draw FILE=vehicle.rb,car.rb CLASS=Vehicle,Car
336
337 *Note* that this will generate a different file for every state machine defined
0b8609e7 » obrie 2008-12-15 Tweak README 338 in the class. The generated files will use an output filename of the format
339 #{class_name}_#{attribute}.#{format}.
6f25124e » jashmenn 2008-12-08 Add rake tasks for generati... Comment 340
341 For examples of actual images generated using this task, see those under the
0b8609e7 » obrie 2008-12-15 Tweak README 342 examples folder.
6f25124e » jashmenn 2008-12-08 Add rake tasks for generati... Comment 343
344 ==== Ruby on Rails Integration
345
346 There is a special integration Rake task for generating state machines for
347 classes used in a Ruby on Rails application. This task will load the application
348 environment, meaning that it's unnecessary to specify the actual file to load.
349
350 For example,
351
352 rake state_machine:draw:rails CLASS=Vehicle
353
354 ==== Merb Integration
355
356 Like Ruby on Rails, there is a special integration Rake task for generating
357 state machines for classes used in a Merb application. This task will load the
358 application environment, meaning that it's unnecessary to specify the actual
359 files to load.
360
361 For example,
362
363 rake state_machine:draw:merb CLASS=Vehicle
364
365 === Interactive graphs
366
8563ca6b » obrie 2007-09-21 More documentation 367 Jean Bovet - {Visual Automata Simulator}[http://www.cs.usfca.edu/~jbovet/vas.html].
368 This is a great tool for "simulating, visualizing and transforming finite state
369 automata and Turing Machines". This tool can help in the creation of states and
370 events for your models. It is cross-platform, written in Java.
b5066674 » obrie 2007-09-21 Move test fixtures out of t... 371
f3565041 » obrie 2008-05-04 Completely rewritten from s... 372 == Testing
373
48d83dfb » obrie 2008-12-14 Add generic attribute predi... Comment 374 To run the entire test suite (will test ActiveRecord, DataMapper, and Sequel
0b8609e7 » obrie 2008-12-15 Tweak README 375 integrations if the proper dependencies are available):
594ad289 » obrie 2008-05-12 Add information about what ... 376
307ac8eb » obrie 2008-12-07 Re-design to be library-agn... 377 rake test
594ad289 » obrie 2008-05-12 Add information about what ... 378
379 == Dependencies
380
307ac8eb » obrie 2008-12-07 Re-design to be library-agn... 381 By default, there are no dependencies. If using specific integrations, those
382 dependencies are listed below.
383
384 * ActiveRecord[http://rubyonrails.org] integration: 2.1.0 or later
385 * DataMapper[http://datamapper.org] integration: 0.9.0 or later
b68d4bda » obrie 2008-12-14 Add Sequel support 386 * Sequel[http://sequel.rubyforge.org] integration: 2.8.0 or later
594ad289 » obrie 2008-05-12 Add information about what ... 387
8563ca6b » obrie 2007-09-21 More documentation 388 == References
389
6a94afcc » obrie 2008-09-06 MAJOR REWRITE! Replace all ... Comment 390 * acts_as_enumeration[http://github.com/pluginaweek/acts_as_enumeration]