verborghs-state_machine 0.9.4

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 (89) hide show
  1. data/CHANGELOG.rdoc +360 -0
  2. data/LICENSE +20 -0
  3. data/README.rdoc +635 -0
  4. data/Rakefile +77 -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/assertions.rb +36 -0
  28. data/lib/state_machine/callback.rb +241 -0
  29. data/lib/state_machine/condition_proxy.rb +106 -0
  30. data/lib/state_machine/eval_helpers.rb +83 -0
  31. data/lib/state_machine/event.rb +267 -0
  32. data/lib/state_machine/event_collection.rb +122 -0
  33. data/lib/state_machine/extensions.rb +149 -0
  34. data/lib/state_machine/guard.rb +230 -0
  35. data/lib/state_machine/initializers/merb.rb +1 -0
  36. data/lib/state_machine/initializers/rails.rb +5 -0
  37. data/lib/state_machine/initializers.rb +4 -0
  38. data/lib/state_machine/integrations/active_model/locale.rb +11 -0
  39. data/lib/state_machine/integrations/active_model/observer.rb +45 -0
  40. data/lib/state_machine/integrations/active_model.rb +445 -0
  41. data/lib/state_machine/integrations/active_record/locale.rb +20 -0
  42. data/lib/state_machine/integrations/active_record.rb +522 -0
  43. data/lib/state_machine/integrations/data_mapper/observer.rb +175 -0
  44. data/lib/state_machine/integrations/data_mapper.rb +379 -0
  45. data/lib/state_machine/integrations/mongo_mapper.rb +309 -0
  46. data/lib/state_machine/integrations/sequel.rb +356 -0
  47. data/lib/state_machine/integrations.rb +83 -0
  48. data/lib/state_machine/machine.rb +1645 -0
  49. data/lib/state_machine/machine_collection.rb +64 -0
  50. data/lib/state_machine/matcher.rb +123 -0
  51. data/lib/state_machine/matcher_helpers.rb +54 -0
  52. data/lib/state_machine/node_collection.rb +152 -0
  53. data/lib/state_machine/state.rb +260 -0
  54. data/lib/state_machine/state_collection.rb +112 -0
  55. data/lib/state_machine/transition.rb +399 -0
  56. data/lib/state_machine/transition_collection.rb +244 -0
  57. data/lib/state_machine.rb +421 -0
  58. data/lib/tasks/state_machine.rake +1 -0
  59. data/lib/tasks/state_machine.rb +27 -0
  60. data/test/files/en.yml +9 -0
  61. data/test/files/switch.rb +11 -0
  62. data/test/functional/state_machine_test.rb +980 -0
  63. data/test/test_helper.rb +4 -0
  64. data/test/unit/assertions_test.rb +40 -0
  65. data/test/unit/callback_test.rb +728 -0
  66. data/test/unit/condition_proxy_test.rb +328 -0
  67. data/test/unit/eval_helpers_test.rb +222 -0
  68. data/test/unit/event_collection_test.rb +324 -0
  69. data/test/unit/event_test.rb +795 -0
  70. data/test/unit/guard_test.rb +909 -0
  71. data/test/unit/integrations/active_model_test.rb +956 -0
  72. data/test/unit/integrations/active_record_test.rb +1918 -0
  73. data/test/unit/integrations/data_mapper_test.rb +1814 -0
  74. data/test/unit/integrations/mongo_mapper_test.rb +1382 -0
  75. data/test/unit/integrations/sequel_test.rb +1492 -0
  76. data/test/unit/integrations_test.rb +50 -0
  77. data/test/unit/invalid_event_test.rb +7 -0
  78. data/test/unit/invalid_transition_test.rb +7 -0
  79. data/test/unit/machine_collection_test.rb +565 -0
  80. data/test/unit/machine_test.rb +2349 -0
  81. data/test/unit/matcher_helpers_test.rb +37 -0
  82. data/test/unit/matcher_test.rb +155 -0
  83. data/test/unit/node_collection_test.rb +207 -0
  84. data/test/unit/state_collection_test.rb +280 -0
  85. data/test/unit/state_machine_test.rb +31 -0
  86. data/test/unit/state_test.rb +848 -0
  87. data/test/unit/transition_collection_test.rb +2098 -0
  88. data/test/unit/transition_test.rb +1384 -0
  89. metadata +176 -0
data/README.rdoc ADDED
@@ -0,0 +1,635 @@
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://rdoc.info/projects/pluginaweek/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/around transition hooks with explicit transition requirements
41
+ * Integration with ActiveModel, ActiveRecord, DataMapper, MongoMapper, and Sequel
42
+ * State predicates
43
+ * State-driven instance / class behavior
44
+ * State values of any data type
45
+ * Dynamically-generated state values
46
+ * Event parallelization
47
+ * Attribute-based event transitions
48
+ * Inheritance
49
+ * Internationalization
50
+ * GraphViz visualization creator
51
+
52
+ Examples of the usage patterns for some of the above features are shown below.
53
+ You can find much more detailed documentation in the actual API.
54
+
55
+ == Usage
56
+
57
+ === Example
58
+
59
+ Below is an example of many of the features offered by this plugin, including:
60
+ * Initial states
61
+ * Namespaced states
62
+ * Transition callbacks
63
+ * Conditional transitions
64
+ * State-driven instance behavior
65
+ * Customized state values
66
+ * Parallel events
67
+
68
+ Class definition:
69
+
70
+ class Vehicle
71
+ attr_accessor :seatbelt_on, :time_used
72
+
73
+ state_machine :state, :initial => :parked do
74
+ before_transition :parked => any - :parked, :do => :put_on_seatbelt
75
+
76
+ after_transition :on => :crash, :do => :tow
77
+ after_transition :on => :repair, :do => :fix
78
+ after_transition any => :parked do |vehicle, transition|
79
+ vehicle.seatbelt_on = false
80
+ end
81
+
82
+ around_transition do |vehicle, transition, block|
83
+ start = Time.now
84
+ block.call
85
+ vehicle.time_used += Time.now - start
86
+ end
87
+
88
+ event :park do
89
+ transition [:idling, :first_gear] => :parked
90
+ end
91
+
92
+ event :ignite do
93
+ transition :stalled => same, :parked => :idling
94
+ end
95
+
96
+ event :idle do
97
+ transition :first_gear => :idling
98
+ end
99
+
100
+ event :shift_up do
101
+ transition :idling => :first_gear, :first_gear => :second_gear, :second_gear => :third_gear
102
+ end
103
+
104
+ event :shift_down do
105
+ transition :third_gear => :second_gear, :second_gear => :first_gear
106
+ end
107
+
108
+ event :crash do
109
+ transition all - [:parked, :stalled] => :stalled, :unless => :auto_shop_busy?
110
+ end
111
+
112
+ event :repair do
113
+ # The first transition that matches the state and passes its conditions
114
+ # will be used
115
+ transition :stalled => :parked, :if => :auto_shop_busy?
116
+ transition :stalled => same
117
+ end
118
+
119
+ state :parked do
120
+ def speed
121
+ 0
122
+ end
123
+ end
124
+
125
+ state :idling, :first_gear do
126
+ def speed
127
+ 10
128
+ end
129
+ end
130
+
131
+ state :second_gear do
132
+ def speed
133
+ 20
134
+ end
135
+ end
136
+ end
137
+
138
+ state_machine :alarm_state, :initial => :active, :namespace => 'alarm' do
139
+ event :enable do
140
+ transition all => :active
141
+ end
142
+
143
+ event :disable do
144
+ transition all => :off
145
+ end
146
+
147
+ state :active, :value => 1
148
+ state :off, :value => 0
149
+ end
150
+
151
+ def initialize
152
+ @seatbelt_on = false
153
+ @time_used = 0
154
+ super() # NOTE: This *must* be called, otherwise states won't get initialized
155
+ end
156
+
157
+ def put_on_seatbelt
158
+ @seatbelt_on = true
159
+ end
160
+
161
+ def auto_shop_busy?
162
+ false
163
+ end
164
+
165
+ def tow
166
+ # tow the vehicle
167
+ end
168
+
169
+ def fix
170
+ # get the vehicle fixed by a mechanic
171
+ end
172
+ end
173
+
174
+ *Note* the comment made on the +initialize+ method in the class. In order for
175
+ state machine attributes to be properly initialized, <tt>super()</tt> must be called.
176
+ See StateMachine::MacroMethods for more information about this.
177
+
178
+ Using the above class as an example, you can interact with the state machine
179
+ like so:
180
+
181
+ vehicle = Vehicle.new # => #<Vehicle:0xb7cf4eac @state="parked", @seatbelt_on=false>
182
+ vehicle.state # => "parked"
183
+ vehicle.state_name # => :parked
184
+ vehicle.human_state_name # => "parked"
185
+ vehicle.parked? # => true
186
+ vehicle.can_ignite? # => true
187
+ vehicle.ignite_transition # => #<StateMachine::Transition attribute=:state event=:ignite from="parked" from_name=:parked to="idling" to_name=:idling>
188
+ vehicle.state_events # => [:ignite]
189
+ vehicle.state_transitions # => [#<StateMachine::Transition attribute=:state event=:ignite from="parked" from_name=:parked to="idling" to_name=:idling>]
190
+ vehicle.speed # => 0
191
+
192
+ vehicle.ignite # => true
193
+ vehicle.parked? # => false
194
+ vehicle.idling? # => true
195
+ vehicle.speed # => 10
196
+ vehicle # => #<Vehicle:0xb7cf4eac @state="idling", @seatbelt_on=true>
197
+
198
+ vehicle.shift_up # => true
199
+ vehicle.speed # => 10
200
+ vehicle # => #<Vehicle:0xb7cf4eac @state="first_gear", @seatbelt_on=true>
201
+
202
+ vehicle.shift_up # => true
203
+ vehicle.speed # => 20
204
+ vehicle # => #<Vehicle:0xb7cf4eac @state="second_gear", @seatbelt_on=true>
205
+
206
+ # The bang (!) operator can raise exceptions if the event fails
207
+ vehicle.park! # => StateMachine::InvalidTransition: Cannot transition state via :park from :second_gear
208
+
209
+ # Generic state predicates can raise exceptions if the value does not exist
210
+ vehicle.state?(:parked) # => false
211
+ vehicle.state?(:invalid) # => IndexError: :invalid is an invalid name
212
+
213
+ # Namespaced machines have uniquely-generated methods
214
+ vehicle.alarm_state # => 1
215
+ vehicle.alarm_state_name # => :active
216
+
217
+ vehicle.can_disable_alarm? # => true
218
+ vehicle.disable_alarm # => true
219
+ vehicle.alarm_state # => 0
220
+ vehicle.alarm_state_name # => :off
221
+ vehicle.can_enable_alarm? # => true
222
+
223
+ vehicle.alarm_off? # => true
224
+ vehicle.alarm_active? # => false
225
+
226
+ # Events can be fired in parallel
227
+ vehicle.fire_events(:shift_down, :enable_alarm) # => true
228
+ vehicle.state_name # => :first_gear
229
+ vehicle.alarm_state_name # => :active
230
+
231
+ vehicle.fire_events!(:ignite, :enable_alarm) # => StateMachine::InvalidTransition: Cannot run events in parallel: ignite, enable_alarm
232
+
233
+ # Human-friendly names can be accessed for states/events
234
+ Vehicle.human_state_name(:first_gear) # => "first gear"
235
+ Vehicle.human_alarm_state_name(:active) # => "active"
236
+
237
+ Vehicle.human_state_event_name(:shift_down) # => "shift down"
238
+ Vehicle.human_alarm_state_event_name(:enable_alarm) # => "enable alarm"
239
+
240
+ == Integrations
241
+
242
+ In addition to being able to define state machines on all Ruby classes, a set of
243
+ out-of-the-box integrations are available for some of the more popular Ruby
244
+ libraries. These integrations add library-specific behavior, allowing for state
245
+ machines to work more tightly with the conventions defined by those libraries.
246
+
247
+ The integrations currently available include:
248
+ * ActiveModel classes
249
+ * ActiveRecord models
250
+ * DataMapper resources
251
+ * MongoMapper models
252
+ * Sequel models
253
+
254
+ A brief overview of these integrations is described below.
255
+
256
+ === ActiveModel
257
+
258
+ The ActiveModel integration is useful for both standalone usage and for providing
259
+ the base implementation for ORMs which implement the ActiveModel API. This
260
+ integration adds support for validation errors, dirty attribute tracking, and
261
+ observers. For example,
262
+
263
+ class Vehicle
264
+ include ActiveModel::Dirty
265
+ include ActiveModel::Validations
266
+ include ActiveModel::Observing
267
+
268
+ attr_accessor :state
269
+ define_attribute_methods [:state]
270
+
271
+ state_machine :initial => :parked do
272
+ before_transition :parked => any - :parked, :do => :put_on_seatbelt
273
+ after_transition any => :parked do |vehicle, transition|
274
+ vehicle.seatbelt = 'off'
275
+ end
276
+ around_transition :benchmark
277
+
278
+ event :ignite do
279
+ transition :parked => :idling
280
+ end
281
+
282
+ state :first_gear, :second_gear do
283
+ validates_presence_of :seatbelt_on
284
+ end
285
+ end
286
+
287
+ def put_on_seatbelt
288
+ ...
289
+ end
290
+
291
+ def benchmark
292
+ ...
293
+ yield
294
+ ...
295
+ end
296
+ end
297
+
298
+ class VehicleObserver < ActiveModel::Observer
299
+ # Callback for :ignite event *before* the transition is performed
300
+ def before_ignite(vehicle, transition)
301
+ # log message
302
+ end
303
+
304
+ # Generic transition callback *after* the transition is performed
305
+ def after_transition(vehicle, transition)
306
+ Audit.log(vehicle, transition)
307
+ end
308
+ end
309
+
310
+ For more information about the various behaviors added for ActiveModel state
311
+ machines and how to build new integrations that use ActiveModel, see
312
+ StateMachine::Integrations::ActiveModel.
313
+
314
+ === ActiveRecord
315
+
316
+ The ActiveRecord integration adds support for database transactions, automatically
317
+ saving the record, named scopes, validation errors, and observers. For example,
318
+
319
+ class Vehicle < ActiveRecord::Base
320
+ state_machine :initial => :parked do
321
+ before_transition :parked => any - :parked, :do => :put_on_seatbelt
322
+ after_transition any => :parked do |vehicle, transition|
323
+ vehicle.seatbelt = 'off'
324
+ end
325
+ around_transition :benchmark
326
+
327
+ event :ignite do
328
+ transition :parked => :idling
329
+ end
330
+
331
+ state :first_gear, :second_gear do
332
+ validates_presence_of :seatbelt_on
333
+ end
334
+ end
335
+
336
+ def put_on_seatbelt
337
+ ...
338
+ end
339
+
340
+ def benchmark
341
+ ...
342
+ yield
343
+ ...
344
+ end
345
+ end
346
+
347
+ class VehicleObserver < ActiveRecord::Observer
348
+ # Callback for :ignite event *before* the transition is performed
349
+ def before_ignite(vehicle, transition)
350
+ # log message
351
+ end
352
+
353
+ # Generic transition callback *after* the transition is performed
354
+ def after_transition(vehicle, transition)
355
+ Audit.log(vehicle, transition)
356
+ end
357
+ end
358
+
359
+ For more information about the various behaviors added for ActiveRecord state
360
+ machines, see StateMachine::Integrations::ActiveRecord.
361
+
362
+ === DataMapper
363
+
364
+ Like the ActiveRecord integration, the DataMapper integration adds support for
365
+ database transactions, automatically saving the record, named scopes, Extlib-like
366
+ callbacks, validation errors, and observers. For example,
367
+
368
+ class Vehicle
369
+ include DataMapper::Resource
370
+
371
+ property :id, Serial
372
+ property :state, String
373
+
374
+ state_machine :initial => :parked do
375
+ before_transition :parked => any - :parked, :do => :put_on_seatbelt
376
+ after_transition any => :parked do |transition|
377
+ self.seatbelt = 'off' # self is the record
378
+ end
379
+ around_transition :benchmark
380
+
381
+ event :ignite do
382
+ transition :parked => :idling
383
+ end
384
+
385
+ state :first_gear, :second_gear do
386
+ validates_presence_of :seatbelt_on
387
+ end
388
+ end
389
+
390
+ def put_on_seatbelt
391
+ ...
392
+ end
393
+
394
+ def benchmark
395
+ ...
396
+ yield
397
+ ...
398
+ end
399
+ end
400
+
401
+ class VehicleObserver
402
+ include DataMapper::Observer
403
+
404
+ observe Vehicle
405
+
406
+ # Callback for :ignite event *before* the transition is performed
407
+ before_transition :on => :ignite do |transition|
408
+ # log message (self is the record)
409
+ end
410
+
411
+ # Generic transition callback *after* the transition is performed
412
+ after_transition do |transition|
413
+ Audit.log(self, transition) # self is the record
414
+ end
415
+
416
+ around_transition do |transition, block|
417
+ # mark start time
418
+ block.call
419
+ # mark stop time
420
+ end
421
+ end
422
+
423
+ *Note* that the DataMapper::Observer integration is optional and only available
424
+ when the dm-observer library is installed.
425
+
426
+ For more information about the various behaviors added for DataMapper state
427
+ machines, see StateMachine::Integrations::DataMapper.
428
+
429
+ === MongoMapper
430
+
431
+ The MongoMapper integration adds support for automatically saving the record,
432
+ basic scopes, validation errors and callbacks. For example,
433
+
434
+ class Vehicle
435
+ include MongoMapper::Document
436
+
437
+ state_machine :initial => :parked do
438
+ before_transition :parked => any - :parked, :do => :put_on_seatbelt
439
+ after_transition any => :parked do |vehicle, transition|
440
+ vehicle.seatbelt = 'off' # self is the record
441
+ end
442
+ around_transition :benchmark
443
+
444
+ event :ignite do
445
+ transition :parked => :idling
446
+ end
447
+
448
+ state :first_gear, :second_gear do
449
+ validates_presence_of :seatbelt_on
450
+ end
451
+ end
452
+
453
+ def put_on_seatbelt
454
+ ...
455
+ end
456
+
457
+ def benchmark
458
+ ...
459
+ yield
460
+ ...
461
+ end
462
+ end
463
+
464
+ For more information about the various behaviors added for MongoMapper state
465
+ machines, see StateMachine::Integrations::MongoMapper.
466
+
467
+ === Sequel
468
+
469
+ Like the ActiveRecord integration, the Sequel integration adds support for
470
+ database transactions, automatically saving the record, named scopes, validation
471
+ errors and callbacks. For example,
472
+
473
+ class Vehicle < Sequel::Model
474
+ state_machine :initial => :parked do
475
+ before_transition :parked => any - :parked, :do => :put_on_seatbelt
476
+ after_transition any => :parked do |transition|
477
+ self.seatbelt = 'off' # self is the record
478
+ end
479
+ around_transition :benchmark
480
+
481
+ event :ignite do
482
+ transition :parked => :idling
483
+ end
484
+
485
+ state :first_gear, :second_gear do
486
+ validates_presence_of :seatbelt_on
487
+ end
488
+ end
489
+
490
+ def put_on_seatbelt
491
+ ...
492
+ end
493
+
494
+ def benchmark
495
+ ...
496
+ yield
497
+ ...
498
+ end
499
+ end
500
+
501
+ For more information about the various behaviors added for Sequel state
502
+ machines, see StateMachine::Integrations::Sequel.
503
+
504
+ == Compatibility
505
+
506
+ Although state_machine introduces a simplified syntax, it still remains
507
+ backwards compatible with previous versions and other state-related libraries.
508
+ For example, transitions and callbacks can continue to be defined like so:
509
+
510
+ class Vehicle
511
+ state_machine :initial => :parked do
512
+ before_transition :from => :parked, :except_to => :parked, :do => :put_on_seatbelt
513
+ after_transition :to => :parked do |transition|
514
+ self.seatbelt = 'off' # self is the record
515
+ end
516
+
517
+ event :ignite do
518
+ transition :from => :parked, :to => :idling
519
+ end
520
+ end
521
+ end
522
+
523
+ Although this verbose syntax will most likely always be supported, it is
524
+ recommended that any state machines eventually migrate to the syntax introduced
525
+ in version 0.6.0.
526
+
527
+ == Tools
528
+
529
+ === Generating graphs
530
+
531
+ This library comes with built-in support for generating di-graphs based on the
532
+ events, states, and transitions defined for a state machine using GraphViz[http://www.graphviz.org].
533
+ This requires that both the <tt>ruby-graphviz</tt> gem and graphviz library be
534
+ installed on the system.
535
+
536
+ ==== Examples
537
+
538
+ To generate a graph for a specific file / class:
539
+
540
+ rake state_machine:draw FILE=vehicle.rb CLASS=Vehicle
541
+
542
+ To save files to a specific path:
543
+
544
+ rake state_machine:draw FILE=vehicle.rb CLASS=Vehicle TARGET=files
545
+
546
+ To customize the image format / orientation:
547
+
548
+ rake state_machine:draw FILE=vehicle.rb CLASS=Vehicle FORMAT=jpg ORIENTATION=landscape
549
+
550
+ To generate multiple state machine graphs:
551
+
552
+ rake state_machine:draw FILE=vehicle.rb,car.rb CLASS=Vehicle,Car
553
+
554
+ *Note* that this will generate a different file for every state machine defined
555
+ in the class. The generated files will use an output filename of the format
556
+ #{class_name}_#{machine_name}.#{format}.
557
+
558
+ For examples of actual images generated using this task, see those under the
559
+ examples folder.
560
+
561
+ ==== Ruby on Rails Integration
562
+
563
+ There is a special integration Rake task for generating state machines for
564
+ classes used in a Ruby on Rails application. This task will load the application
565
+ environment, meaning that it's unnecessary to specify the actual file to load.
566
+
567
+ For example,
568
+
569
+ rake state_machine:draw CLASS=Vehicle
570
+
571
+ If you are using this library as a gem in Rails 2.x, the following must be added
572
+ to the end of your application's Rakefile in order for the above task to work:
573
+
574
+ require 'tasks/state_machine'
575
+
576
+ If you are using Rails 3.0+, you must also add the following to your
577
+ application's Gemfile:
578
+
579
+ gem 'ruby-graphviz', :require => 'graphviz'
580
+
581
+ ==== Merb Integration
582
+
583
+ Like Ruby on Rails, there is a special integration Rake task for generating
584
+ state machines for classes used in a Merb application. This task will load the
585
+ application environment, meaning that it's unnecessary to specify the actual
586
+ files to load.
587
+
588
+ For example,
589
+
590
+ rake state_machine:draw CLASS=Vehicle
591
+
592
+ === Interactive graphs
593
+
594
+ Jean Bovet's {Visual Automata Simulator}[http://www.cs.usfca.edu/~jbovet/vas.html]
595
+ is a great tool for "simulating, visualizing and transforming finite state
596
+ automata and Turing Machines". It can help in the creation of states and events
597
+ for your models. It is cross-platform, written in Java.
598
+
599
+ == Testing
600
+
601
+ To run the core test suite (does *not* test any of the integrations):
602
+
603
+ rake test
604
+
605
+ Test specific versions of integrations like so:
606
+
607
+ rake test INTEGRATION=active_model VERSION=3.0.0
608
+ rake test INTEGRATION=active_record VERSION=2.0.0
609
+ rake test INTEGRATION=data_mapper VERSION=0.9.4
610
+ rake test INTEGRATION=mongo_mapper VERSION=0.5.5
611
+ rake test INTEGRATION=sequel VERSION=2.8.0
612
+
613
+ == Caveats
614
+
615
+ The following caveats should be noted when using state_machine:
616
+
617
+ * DataMapper: Attribute-based event transitions are disabled when dm-validations 0.9.4 - 0.9.6 is in use
618
+ * Overridden event methods won't get invoked when using attribute-based event transitions
619
+ * around_transition callbacks in ORM integrations won't work on JRuby since it doesn't support continuations
620
+
621
+ == Dependencies
622
+
623
+ * Ruby 1.8.6 or later
624
+
625
+ If using specific integrations:
626
+
627
+ * ActiveModel[http://rubyonrails.org] integration: 3.0.0 or later
628
+ * ActiveRecord[http://rubyonrails.org] integration: 2.0.0 or later
629
+ * DataMapper[http://datamapper.org] integration: 0.9.4 or later
630
+ * MongoMapper[http://mongomapper.com] integration: 0.5.5 or later
631
+ * Sequel[http://sequel.rubyforge.org] integration: 2.8.0 or later
632
+
633
+ If graphing state machine:
634
+
635
+ * ruby-graphviz[http://github.com/glejeune/Ruby-Graphviz]: 0.9.0 or later
data/Rakefile ADDED
@@ -0,0 +1,77 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+ require 'rake/testtask'
4
+ require 'rake/rdoctask'
5
+ require 'rake/gempackagetask'
6
+
7
+ spec = Gem::Specification.new do |s|
8
+ s.name = 'state_machine'
9
+ s.version = '0.9.4'
10
+ s.platform = Gem::Platform::RUBY
11
+ s.summary = 'Adds support for creating state machines for attributes on any Ruby class'
12
+ s.description = s.summary
13
+
14
+ s.files = FileList['{examples,lib,test}/**/*'] + %w(CHANGELOG.rdoc init.rb LICENSE Rakefile README.rdoc) - FileList['test/*.log']
15
+ s.require_path = 'lib'
16
+ s.has_rdoc = true
17
+ s.test_files = Dir['test/**/*_test.rb']
18
+
19
+ s.author = 'Aaron Pfeifer'
20
+ s.email = 'aaron@pluginaweek.org'
21
+ s.homepage = 'http://www.pluginaweek.org'
22
+ s.rubyforge_project = 'pluginaweek'
23
+ end
24
+
25
+ desc 'Default: run all tests.'
26
+ task :default => :test
27
+
28
+ desc "Test the #{spec.name} plugin."
29
+ Rake::TestTask.new(:test) do |t|
30
+ t.libs << 'lib'
31
+ t.test_files = ENV['INTEGRATION'] ? Dir["test/unit/integrations/#{ENV['INTEGRATION']}_test.rb"] : Dir['test/{functional,unit}/*_test.rb']
32
+ t.verbose = true
33
+ end
34
+
35
+ begin
36
+ require 'rcov/rcovtask'
37
+ namespace :test do
38
+ desc "Test the #{spec.name} plugin with Rcov."
39
+ Rcov::RcovTask.new(:rcov) do |t|
40
+ t.libs << 'lib'
41
+ t.test_files = spec.test_files
42
+ t.rcov_opts << '--exclude="^(?!lib/)"'
43
+ t.verbose = true
44
+ end
45
+ end
46
+ rescue LoadError
47
+ end
48
+
49
+ desc "Generate documentation for the #{spec.name} plugin."
50
+ Rake::RDocTask.new(:rdoc) do |rdoc|
51
+ rdoc.rdoc_dir = 'rdoc'
52
+ rdoc.title = spec.name
53
+ rdoc.template = '../rdoc_template.rb'
54
+ rdoc.options << '--line-numbers' << '--inline-source'
55
+ rdoc.rdoc_files.include('README.rdoc', 'CHANGELOG.rdoc', 'LICENSE', 'lib/**/*.rb')
56
+ end
57
+
58
+ desc 'Generate a gemspec file.'
59
+ task :gemspec do
60
+ File.open("#{spec.name}.gemspec", 'w') do |f|
61
+ f.write spec.to_ruby
62
+ end
63
+ end
64
+
65
+ Rake::GemPackageTask.new(spec) do |p|
66
+ p.gem_spec = spec
67
+ end
68
+
69
+ desc 'Publish the release files to RubyForge.'
70
+ task :release => :package do
71
+ require 'rake/gemcutter'
72
+
73
+ Rake::Gemcutter::Tasks.new(spec)
74
+ Rake::Task['gem:push'].invoke
75
+ end
76
+
77
+ load File.dirname(__FILE__) + '/lib/tasks/state_machine.rake'
Binary file
Binary file
Binary file