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

