state_machine 1.0.2 → 1.0.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (102) hide show
  1. data/.gitignore +1 -0
  2. data/.travis.yml +0 -2
  3. data/.yardopts +3 -2
  4. data/Appraisals +48 -0
  5. data/{CHANGELOG.rdoc → CHANGELOG.md} +63 -46
  6. data/README.md +1029 -0
  7. data/gemfiles/active_model-3.0.0.gemfile.lock +1 -3
  8. data/gemfiles/active_model-3.0.5.gemfile.lock +1 -3
  9. data/gemfiles/active_model-3.1.1.gemfile +7 -0
  10. data/gemfiles/active_model-3.1.1.gemfile.lock +32 -0
  11. data/gemfiles/active_record-2.0.0.gemfile.lock +1 -3
  12. data/gemfiles/active_record-2.0.5.gemfile.lock +1 -3
  13. data/gemfiles/active_record-2.1.0.gemfile.lock +1 -3
  14. data/gemfiles/active_record-2.1.2.gemfile.lock +1 -3
  15. data/gemfiles/active_record-2.2.3.gemfile.lock +1 -3
  16. data/gemfiles/active_record-2.3.12.gemfile.lock +1 -3
  17. data/gemfiles/active_record-3.0.0.gemfile.lock +1 -3
  18. data/gemfiles/active_record-3.0.5.gemfile.lock +1 -3
  19. data/gemfiles/active_record-3.1.1.gemfile +8 -0
  20. data/gemfiles/active_record-3.1.1.gemfile.lock +43 -0
  21. data/gemfiles/data_mapper-0.10.2.gemfile.lock +1 -3
  22. data/gemfiles/data_mapper-0.9.11.gemfile.lock +1 -3
  23. data/gemfiles/data_mapper-0.9.4.gemfile.lock +1 -3
  24. data/gemfiles/data_mapper-0.9.7.gemfile.lock +1 -3
  25. data/gemfiles/data_mapper-1.0.0.gemfile.lock +1 -3
  26. data/gemfiles/data_mapper-1.0.1.gemfile.lock +1 -3
  27. data/gemfiles/data_mapper-1.0.2.gemfile.lock +1 -3
  28. data/gemfiles/data_mapper-1.1.0.gemfile.lock +1 -3
  29. data/gemfiles/data_mapper-1.2.0.gemfile +12 -0
  30. data/gemfiles/data_mapper-1.2.0.gemfile.lock +49 -0
  31. data/gemfiles/default.gemfile.lock +1 -3
  32. data/gemfiles/graphviz-0.9.0.gemfile +7 -0
  33. data/gemfiles/graphviz-0.9.0.gemfile.lock +24 -0
  34. data/gemfiles/graphviz-0.9.21.gemfile +7 -0
  35. data/gemfiles/graphviz-0.9.21.gemfile.lock +24 -0
  36. data/gemfiles/graphviz-1.0.0.gemfile +7 -0
  37. data/gemfiles/graphviz-1.0.0.gemfile.lock +24 -0
  38. data/gemfiles/mongo_mapper-0.10.0.gemfile +7 -0
  39. data/gemfiles/mongo_mapper-0.10.0.gemfile.lock +41 -0
  40. data/gemfiles/mongo_mapper-0.5.5.gemfile.lock +1 -3
  41. data/gemfiles/mongo_mapper-0.5.8.gemfile.lock +1 -3
  42. data/gemfiles/mongo_mapper-0.6.0.gemfile.lock +1 -3
  43. data/gemfiles/mongo_mapper-0.6.10.gemfile.lock +1 -3
  44. data/gemfiles/mongo_mapper-0.7.0.gemfile.lock +1 -3
  45. data/gemfiles/mongo_mapper-0.7.5.gemfile.lock +1 -3
  46. data/gemfiles/mongo_mapper-0.8.0.gemfile.lock +1 -3
  47. data/gemfiles/mongo_mapper-0.8.3.gemfile.lock +1 -3
  48. data/gemfiles/mongo_mapper-0.8.4.gemfile.lock +1 -3
  49. data/gemfiles/mongo_mapper-0.8.6.gemfile.lock +1 -3
  50. data/gemfiles/mongo_mapper-0.9.0.gemfile.lock +1 -3
  51. data/gemfiles/mongoid-2.0.0.gemfile.lock +1 -3
  52. data/gemfiles/mongoid-2.1.4.gemfile.lock +1 -3
  53. data/gemfiles/mongoid-2.2.4.gemfile +7 -0
  54. data/gemfiles/mongoid-2.2.4.gemfile.lock +40 -0
  55. data/gemfiles/mongoid-2.3.3.gemfile +7 -0
  56. data/gemfiles/mongoid-2.3.3.gemfile.lock +40 -0
  57. data/gemfiles/sequel-2.11.0.gemfile.lock +1 -3
  58. data/gemfiles/sequel-2.12.0.gemfile.lock +1 -3
  59. data/gemfiles/sequel-2.8.0.gemfile.lock +1 -3
  60. data/gemfiles/sequel-3.0.0.gemfile.lock +1 -3
  61. data/gemfiles/sequel-3.13.0.gemfile.lock +1 -3
  62. data/gemfiles/sequel-3.14.0.gemfile.lock +1 -3
  63. data/gemfiles/sequel-3.23.0.gemfile.lock +1 -3
  64. data/gemfiles/sequel-3.24.0.gemfile.lock +1 -3
  65. data/gemfiles/sequel-3.29.0.gemfile +8 -0
  66. data/gemfiles/sequel-3.29.0.gemfile.lock +26 -0
  67. data/lib/state_machine.rb +45 -0
  68. data/lib/state_machine/event.rb +18 -3
  69. data/lib/state_machine/event_collection.rb +1 -1
  70. data/lib/state_machine/integrations/active_model.rb +59 -16
  71. data/lib/state_machine/integrations/active_model/observer.rb +3 -15
  72. data/lib/state_machine/integrations/active_record.rb +46 -9
  73. data/lib/state_machine/integrations/data_mapper.rb +42 -2
  74. data/lib/state_machine/integrations/data_mapper/versions.rb +22 -10
  75. data/lib/state_machine/integrations/mongo_mapper.rb +55 -0
  76. data/lib/state_machine/integrations/mongo_mapper/versions.rb +3 -3
  77. data/lib/state_machine/integrations/mongoid.rb +57 -12
  78. data/lib/state_machine/integrations/mongoid/versions.rb +22 -4
  79. data/lib/state_machine/integrations/sequel.rb +45 -0
  80. data/lib/state_machine/integrations/sequel/versions.rb +3 -0
  81. data/lib/state_machine/machine.rb +148 -34
  82. data/lib/state_machine/node_collection.rb +36 -3
  83. data/lib/state_machine/state.rb +6 -3
  84. data/lib/state_machine/state_collection.rb +1 -1
  85. data/lib/state_machine/version.rb +1 -1
  86. data/lib/tasks/state_machine.rb +11 -9
  87. data/state_machine.gemspec +2 -3
  88. data/test/functional/state_machine_test.rb +54 -1
  89. data/test/unit/event_collection_test.rb +4 -0
  90. data/test/unit/event_test.rb +34 -1
  91. data/test/unit/integrations/active_model_test.rb +80 -0
  92. data/test/unit/integrations/active_record_test.rb +105 -2
  93. data/test/unit/integrations/data_mapper_test.rb +27 -25
  94. data/test/unit/integrations/mongo_mapper_test.rb +80 -25
  95. data/test/unit/integrations/mongoid_test.rb +61 -6
  96. data/test/unit/integrations/sequel_test.rb +8 -2
  97. data/test/unit/machine_test.rb +87 -9
  98. data/test/unit/node_collection_test.rb +129 -12
  99. data/test/unit/state_collection_test.rb +4 -0
  100. data/test/unit/state_test.rb +2 -2
  101. metadata +30 -24
  102. data/README.rdoc +0 -844
@@ -41,6 +41,10 @@ class StateCollectionTest < Test::Unit::TestCase
41
41
  assert_equal @parked, @states[:parked]
42
42
  end
43
43
 
44
+ def test_should_index_by_string_name
45
+ assert_equal @parked, @states['parked']
46
+ end
47
+
44
48
  def test_should_index_by_qualified_name
45
49
  assert_equal @parked, @states[:parked, :qualified_name]
46
50
  end
@@ -479,7 +479,7 @@ class StateWithConflictingHelpersBeforeDefinitionTest < Test::Unit::TestCase
479
479
  end
480
480
 
481
481
  def test_should_output_warning
482
- assert_equal "Instance method \"parked?\" is already defined in #{@superclass.to_s}, use generic helper instead.\n", $stderr.string
482
+ assert_equal "Instance method \"parked?\" is already defined in #{@superclass.to_s}, use generic helper instead or set StateMachine::Machine.ignore_method_conflicts = true.\n", $stderr.string
483
483
  end
484
484
 
485
485
  def teardown
@@ -572,7 +572,7 @@ class StateWithConflictingMachineNameTest < Test::Unit::TestCase
572
572
 
573
573
  def test_should_output_warning_if_name_conflicts
574
574
  StateMachine::State.new(@state_machine, :state)
575
- assert_equal "Instance method \"state?\" is already defined in #{@klass} :state instance helpers, use generic helper instead.\n", $stderr.string
575
+ assert_equal "Instance method \"state?\" is already defined in #{@klass} :state instance helpers, use generic helper instead or set StateMachine::Machine.ignore_method_conflicts = true.\n", $stderr.string
576
576
  end
577
577
 
578
578
  def teardown
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: state_machine
3
3
  version: !ruby/object:Gem::Version
4
- hash: 19
4
+ hash: 17
5
5
  prerelease:
6
6
  segments:
7
7
  - 1
8
8
  - 0
9
- - 2
10
- version: 1.0.2
9
+ - 3
10
+ version: 1.0.3
11
11
  platform: ruby
12
12
  authors:
13
13
  - Aaron Pfeifer
@@ -15,7 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2011-08-10 00:00:00 Z
18
+ date: 2011-11-05 00:00:00 Z
19
19
  dependencies:
20
20
  - !ruby/object:Gem::Dependency
21
21
  name: rake
@@ -61,21 +61,6 @@ dependencies:
61
61
  version: 0.3.8
62
62
  type: :development
63
63
  version_requirements: *id003
64
- - !ruby/object:Gem::Dependency
65
- name: ruby-graphviz
66
- prerelease: false
67
- requirement: &id004 !ruby/object:Gem::Requirement
68
- none: false
69
- requirements:
70
- - - ~>
71
- - !ruby/object:Gem::Version
72
- hash: 15
73
- segments:
74
- - 1
75
- - 0
76
- version: "1.0"
77
- type: :development
78
- version_requirements: *id004
79
64
  description: Adds support for creating state machines for attributes on any Ruby class
80
65
  email: aaron@pluginaweek.org
81
66
  executables: []
@@ -83,18 +68,18 @@ executables: []
83
68
  extensions: []
84
69
 
85
70
  extra_rdoc_files:
86
- - README.rdoc
87
- - CHANGELOG.rdoc
71
+ - README.md
72
+ - CHANGELOG.md
88
73
  - LICENSE
89
74
  files:
90
75
  - .gitignore
91
76
  - .travis.yml
92
77
  - .yardopts
93
78
  - Appraisals
94
- - CHANGELOG.rdoc
79
+ - CHANGELOG.md
95
80
  - Gemfile
96
81
  - LICENSE
97
- - README.rdoc
82
+ - README.md
98
83
  - Rakefile
99
84
  - examples/AutoShop_state.png
100
85
  - examples/Car_state.png
@@ -121,6 +106,8 @@ files:
121
106
  - gemfiles/active_model-3.0.0.gemfile.lock
122
107
  - gemfiles/active_model-3.0.5.gemfile
123
108
  - gemfiles/active_model-3.0.5.gemfile.lock
109
+ - gemfiles/active_model-3.1.1.gemfile
110
+ - gemfiles/active_model-3.1.1.gemfile.lock
124
111
  - gemfiles/active_record-2.0.0.gemfile
125
112
  - gemfiles/active_record-2.0.0.gemfile.lock
126
113
  - gemfiles/active_record-2.0.5.gemfile
@@ -137,6 +124,8 @@ files:
137
124
  - gemfiles/active_record-3.0.0.gemfile.lock
138
125
  - gemfiles/active_record-3.0.5.gemfile
139
126
  - gemfiles/active_record-3.0.5.gemfile.lock
127
+ - gemfiles/active_record-3.1.1.gemfile
128
+ - gemfiles/active_record-3.1.1.gemfile.lock
140
129
  - gemfiles/data_mapper-0.10.2.gemfile
141
130
  - gemfiles/data_mapper-0.10.2.gemfile.lock
142
131
  - gemfiles/data_mapper-0.9.11.gemfile
@@ -153,8 +142,18 @@ files:
153
142
  - gemfiles/data_mapper-1.0.2.gemfile.lock
154
143
  - gemfiles/data_mapper-1.1.0.gemfile
155
144
  - gemfiles/data_mapper-1.1.0.gemfile.lock
145
+ - gemfiles/data_mapper-1.2.0.gemfile
146
+ - gemfiles/data_mapper-1.2.0.gemfile.lock
156
147
  - gemfiles/default.gemfile
157
148
  - gemfiles/default.gemfile.lock
149
+ - gemfiles/graphviz-0.9.0.gemfile
150
+ - gemfiles/graphviz-0.9.0.gemfile.lock
151
+ - gemfiles/graphviz-0.9.21.gemfile
152
+ - gemfiles/graphviz-0.9.21.gemfile.lock
153
+ - gemfiles/graphviz-1.0.0.gemfile
154
+ - gemfiles/graphviz-1.0.0.gemfile.lock
155
+ - gemfiles/mongo_mapper-0.10.0.gemfile
156
+ - gemfiles/mongo_mapper-0.10.0.gemfile.lock
158
157
  - gemfiles/mongo_mapper-0.5.5.gemfile
159
158
  - gemfiles/mongo_mapper-0.5.5.gemfile.lock
160
159
  - gemfiles/mongo_mapper-0.5.8.gemfile
@@ -181,6 +180,10 @@ files:
181
180
  - gemfiles/mongoid-2.0.0.gemfile.lock
182
181
  - gemfiles/mongoid-2.1.4.gemfile
183
182
  - gemfiles/mongoid-2.1.4.gemfile.lock
183
+ - gemfiles/mongoid-2.2.4.gemfile
184
+ - gemfiles/mongoid-2.2.4.gemfile.lock
185
+ - gemfiles/mongoid-2.3.3.gemfile
186
+ - gemfiles/mongoid-2.3.3.gemfile.lock
184
187
  - gemfiles/sequel-2.11.0.gemfile
185
188
  - gemfiles/sequel-2.11.0.gemfile.lock
186
189
  - gemfiles/sequel-2.12.0.gemfile
@@ -197,6 +200,8 @@ files:
197
200
  - gemfiles/sequel-3.23.0.gemfile.lock
198
201
  - gemfiles/sequel-3.24.0.gemfile
199
202
  - gemfiles/sequel-3.24.0.gemfile.lock
203
+ - gemfiles/sequel-3.29.0.gemfile
204
+ - gemfiles/sequel-3.29.0.gemfile.lock
200
205
  - init.rb
201
206
  - lib/state_machine.rb
202
207
  - lib/state_machine/assertions.rb
@@ -293,7 +298,7 @@ rdoc_options:
293
298
  - --title
294
299
  - state_machine
295
300
  - --main
296
- - README.rdoc
301
+ - README.md
297
302
  require_paths:
298
303
  - lib
299
304
  required_ruby_version: !ruby/object:Gem::Requirement
@@ -358,3 +363,4 @@ test_files:
358
363
  - test/unit/state_test.rb
359
364
  - test/unit/transition_collection_test.rb
360
365
  - test/unit/transition_test.rb
366
+ has_rdoc:
@@ -1,844 +0,0 @@
1
- == state_machine http://travis-ci.org/pluginaweek/state_machine.png
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/github/pluginaweek/state_machine/master/frames
11
-
12
- Bugs
13
-
14
- * http://github.com/pluginaweek/state_machine/issues
15
-
16
- Development
17
-
18
- * http://github.com/pluginaweek/state_machine
19
-
20
- Testing
21
-
22
- * http://travis-ci.org/pluginaweek/state_machine
23
-
24
- Source
25
-
26
- * git://github.com/pluginaweek/state_machine.git
27
-
28
- Mailing List
29
-
30
- * http://groups.google.com/group/pluginaweek-talk
31
-
32
- == Description
33
-
34
- State machines make it dead-simple to manage the behavior of a class. Too often,
35
- the state of an object is kept by creating multiple boolean attributes and
36
- deciding how to behave based on the values. This can become cumbersome and
37
- difficult to maintain when the complexity of your class starts to increase.
38
-
39
- +state_machine+ simplifies this design by introducing the various parts of a real
40
- state machine, including states, events, transitions, and callbacks. However,
41
- the api is designed to be so simple you don't even need to know what a
42
- state machine is :)
43
-
44
- Some brief, high-level features include:
45
- * Defining state machines on any Ruby class
46
- * Multiple state machines on a single class
47
- * Namespaced state machines
48
- * before/after/around/failure transition hooks with explicit transition requirements
49
- * Integration with ActiveModel, ActiveRecord, DataMapper, Mongoid, MongoMapper, and Sequel
50
- * State predicates
51
- * State-driven instance / class behavior
52
- * State values of any data type
53
- * Dynamically-generated state values
54
- * Event parallelization
55
- * Attribute-based event transitions
56
- * Path analysis
57
- * Inheritance
58
- * Internationalization
59
- * GraphViz visualization creator
60
- * Flexible machine syntax
61
-
62
- Examples of the usage patterns for some of the above features are shown below.
63
- You can find much more detailed documentation in the actual API.
64
-
65
- == Usage
66
-
67
- === Example
68
-
69
- Below is an example of many of the features offered by this plugin, including:
70
- * Initial states
71
- * Namespaced states
72
- * Transition callbacks
73
- * Conditional transitions
74
- * State-driven instance behavior
75
- * Customized state values
76
- * Parallel events
77
- * Path analysis
78
-
79
- Class definition:
80
-
81
- class Vehicle
82
- attr_accessor :seatbelt_on, :time_used
83
-
84
- state_machine :state, :initial => :parked do
85
- before_transition :parked => any - :parked, :do => :put_on_seatbelt
86
-
87
- after_transition :on => :crash, :do => :tow
88
- after_transition :on => :repair, :do => :fix
89
- after_transition any => :parked do |vehicle, transition|
90
- vehicle.seatbelt_on = false
91
- end
92
-
93
- after_failure :on => :ignite, :do => :log_start_failure
94
-
95
- around_transition do |vehicle, transition, block|
96
- start = Time.now
97
- block.call
98
- vehicle.time_used += Time.now - start
99
- end
100
-
101
- event :park do
102
- transition [:idling, :first_gear] => :parked
103
- end
104
-
105
- event :ignite do
106
- transition :stalled => same, :parked => :idling
107
- end
108
-
109
- event :idle do
110
- transition :first_gear => :idling
111
- end
112
-
113
- event :shift_up do
114
- transition :idling => :first_gear, :first_gear => :second_gear, :second_gear => :third_gear
115
- end
116
-
117
- event :shift_down do
118
- transition :third_gear => :second_gear, :second_gear => :first_gear
119
- end
120
-
121
- event :crash do
122
- transition all - [:parked, :stalled] => :stalled, :unless => :auto_shop_busy?
123
- end
124
-
125
- event :repair do
126
- # The first transition that matches the state and passes its conditions
127
- # will be used
128
- transition :stalled => :parked, :if => :auto_shop_busy?
129
- transition :stalled => same
130
- end
131
-
132
- state :parked do
133
- def speed
134
- 0
135
- end
136
- end
137
-
138
- state :idling, :first_gear do
139
- def speed
140
- 10
141
- end
142
- end
143
-
144
- state :second_gear do
145
- def speed
146
- 20
147
- end
148
- end
149
- end
150
-
151
- state_machine :alarm_state, :initial => :active, :namespace => 'alarm' do
152
- event :enable do
153
- transition all => :active
154
- end
155
-
156
- event :disable do
157
- transition all => :off
158
- end
159
-
160
- state :active, :value => 1
161
- state :off, :value => 0
162
- end
163
-
164
- def initialize
165
- @seatbelt_on = false
166
- @time_used = 0
167
- super() # NOTE: This *must* be called, otherwise states won't get initialized
168
- end
169
-
170
- def put_on_seatbelt
171
- @seatbelt_on = true
172
- end
173
-
174
- def auto_shop_busy?
175
- false
176
- end
177
-
178
- def tow
179
- # tow the vehicle
180
- end
181
-
182
- def fix
183
- # get the vehicle fixed by a mechanic
184
- end
185
-
186
- def log_start_failure
187
- # log a failed attempt to start the vehicle
188
- end
189
- end
190
-
191
- *Note* the comment made on the +initialize+ method in the class. In order for
192
- state machine attributes to be properly initialized, <tt>super()</tt> must be called.
193
- See StateMachine::MacroMethods for more information about this.
194
-
195
- Using the above class as an example, you can interact with the state machine
196
- like so:
197
-
198
- vehicle = Vehicle.new # => #<Vehicle:0xb7cf4eac @state="parked", @seatbelt_on=false>
199
- vehicle.state # => "parked"
200
- vehicle.state_name # => :parked
201
- vehicle.human_state_name # => "parked"
202
- vehicle.parked? # => true
203
- vehicle.can_ignite? # => true
204
- vehicle.ignite_transition # => #<StateMachine::Transition attribute=:state event=:ignite from="parked" from_name=:parked to="idling" to_name=:idling>
205
- vehicle.state_events # => [:ignite]
206
- vehicle.state_transitions # => [#<StateMachine::Transition attribute=:state event=:ignite from="parked" from_name=:parked to="idling" to_name=:idling>]
207
- vehicle.speed # => 0
208
-
209
- vehicle.ignite # => true
210
- vehicle.parked? # => false
211
- vehicle.idling? # => true
212
- vehicle.speed # => 10
213
- vehicle # => #<Vehicle:0xb7cf4eac @state="idling", @seatbelt_on=true>
214
-
215
- vehicle.shift_up # => true
216
- vehicle.speed # => 10
217
- vehicle # => #<Vehicle:0xb7cf4eac @state="first_gear", @seatbelt_on=true>
218
-
219
- vehicle.shift_up # => true
220
- vehicle.speed # => 20
221
- vehicle # => #<Vehicle:0xb7cf4eac @state="second_gear", @seatbelt_on=true>
222
-
223
- # The bang (!) operator can raise exceptions if the event fails
224
- vehicle.park! # => StateMachine::InvalidTransition: Cannot transition state via :park from :second_gear
225
-
226
- # Generic state predicates can raise exceptions if the value does not exist
227
- vehicle.state?(:parked) # => false
228
- vehicle.state?(:invalid) # => IndexError: :invalid is an invalid name
229
-
230
- # Namespaced machines have uniquely-generated methods
231
- vehicle.alarm_state # => 1
232
- vehicle.alarm_state_name # => :active
233
-
234
- vehicle.can_disable_alarm? # => true
235
- vehicle.disable_alarm # => true
236
- vehicle.alarm_state # => 0
237
- vehicle.alarm_state_name # => :off
238
- vehicle.can_enable_alarm? # => true
239
-
240
- vehicle.alarm_off? # => true
241
- vehicle.alarm_active? # => false
242
-
243
- # Events can be fired in parallel
244
- vehicle.fire_events(:shift_down, :enable_alarm) # => true
245
- vehicle.state_name # => :first_gear
246
- vehicle.alarm_state_name # => :active
247
-
248
- vehicle.fire_events!(:ignite, :enable_alarm) # => StateMachine::InvalidTransition: Cannot run events in parallel: ignite, enable_alarm
249
-
250
- # Human-friendly names can be accessed for states/events
251
- Vehicle.human_state_name(:first_gear) # => "first gear"
252
- Vehicle.human_alarm_state_name(:active) # => "active"
253
-
254
- Vehicle.human_state_event_name(:shift_down) # => "shift down"
255
- Vehicle.human_alarm_state_event_name(:enable) # => "enable"
256
-
257
- # Available transition paths can be analyzed for an object
258
- vehicle.state_paths # => [[#<StateMachine::Transition ...], [#<StateMachine::Transition ...], ...]
259
- vehicle.state_paths.to_states # => [:parked, :idling, :first_gear, :stalled, :second_gear, :third_gear]
260
- vehicle.state_paths.events # => [:park, :ignite, :shift_up, :idle, :crash, :repair, :shift_down]
261
-
262
- # Find all paths that start and end on certain states
263
- vehicle.state_paths(:from => :parked, :to => :first_gear) # => [[
264
- # #<StateMachine::Transition attribute=:state event=:ignite from="parked" ...>,
265
- # #<StateMachine::Transition attribute=:state event=:shift_up from="idling" ...>
266
- # ]]
267
-
268
- == Integrations
269
-
270
- In addition to being able to define state machines on all Ruby classes, a set of
271
- out-of-the-box integrations are available for some of the more popular Ruby
272
- libraries. These integrations add library-specific behavior, allowing for state
273
- machines to work more tightly with the conventions defined by those libraries.
274
-
275
- The integrations currently available include:
276
- * ActiveModel classes
277
- * ActiveRecord models
278
- * DataMapper resources
279
- * Mongoid models
280
- * MongoMapper models
281
- * Sequel models
282
-
283
- A brief overview of these integrations is described below.
284
-
285
- === ActiveModel
286
-
287
- The ActiveModel integration is useful for both standalone usage and for providing
288
- the base implementation for ORMs which implement the ActiveModel API. This
289
- integration adds support for validation errors, dirty attribute tracking, and
290
- observers. For example,
291
-
292
- class Vehicle
293
- include ActiveModel::Dirty
294
- include ActiveModel::Validations
295
- include ActiveModel::Observing
296
-
297
- attr_accessor :state
298
- define_attribute_methods [:state]
299
-
300
- state_machine :initial => :parked do
301
- before_transition :parked => any - :parked, :do => :put_on_seatbelt
302
- after_transition any => :parked do |vehicle, transition|
303
- vehicle.seatbelt = 'off'
304
- end
305
- around_transition :benchmark
306
-
307
- event :ignite do
308
- transition :parked => :idling
309
- end
310
-
311
- state :first_gear, :second_gear do
312
- validates_presence_of :seatbelt_on
313
- end
314
- end
315
-
316
- def put_on_seatbelt
317
- ...
318
- end
319
-
320
- def benchmark
321
- ...
322
- yield
323
- ...
324
- end
325
- end
326
-
327
- class VehicleObserver < ActiveModel::Observer
328
- # Callback for :ignite event *before* the transition is performed
329
- def before_ignite(vehicle, transition)
330
- # log message
331
- end
332
-
333
- # Generic transition callback *after* the transition is performed
334
- def after_transition(vehicle, transition)
335
- Audit.log(vehicle, transition)
336
- end
337
-
338
- # Generic callback after the transition fails to perform
339
- def after_failure_to_transition(vehicle, transition)
340
- Audit.error(vehicle, transition)
341
- end
342
- end
343
-
344
- For more information about the various behaviors added for ActiveModel state
345
- machines and how to build new integrations that use ActiveModel, see
346
- StateMachine::Integrations::ActiveModel.
347
-
348
- === ActiveRecord
349
-
350
- The ActiveRecord integration adds support for database transactions, automatically
351
- saving the record, named scopes, validation errors, and observers. For example,
352
-
353
- class Vehicle < ActiveRecord::Base
354
- state_machine :initial => :parked do
355
- before_transition :parked => any - :parked, :do => :put_on_seatbelt
356
- after_transition any => :parked do |vehicle, transition|
357
- vehicle.seatbelt = 'off'
358
- end
359
- around_transition :benchmark
360
-
361
- event :ignite do
362
- transition :parked => :idling
363
- end
364
-
365
- state :first_gear, :second_gear do
366
- validates_presence_of :seatbelt_on
367
- end
368
- end
369
-
370
- def put_on_seatbelt
371
- ...
372
- end
373
-
374
- def benchmark
375
- ...
376
- yield
377
- ...
378
- end
379
- end
380
-
381
- class VehicleObserver < ActiveRecord::Observer
382
- # Callback for :ignite event *before* the transition is performed
383
- def before_ignite(vehicle, transition)
384
- # log message
385
- end
386
-
387
- # Generic transition callback *after* the transition is performed
388
- def after_transition(vehicle, transition)
389
- Audit.log(vehicle, transition)
390
- end
391
- end
392
-
393
- For more information about the various behaviors added for ActiveRecord state
394
- machines, see StateMachine::Integrations::ActiveRecord.
395
-
396
- === DataMapper
397
-
398
- Like the ActiveRecord integration, the DataMapper integration adds support for
399
- database transactions, automatically saving the record, named scopes, Extlib-like
400
- callbacks, validation errors, and observers. For example,
401
-
402
- class Vehicle
403
- include DataMapper::Resource
404
-
405
- property :id, Serial
406
- property :state, String
407
-
408
- state_machine :initial => :parked do
409
- before_transition :parked => any - :parked, :do => :put_on_seatbelt
410
- after_transition any => :parked do |transition|
411
- self.seatbelt = 'off' # self is the record
412
- end
413
- around_transition :benchmark
414
-
415
- event :ignite do
416
- transition :parked => :idling
417
- end
418
-
419
- state :first_gear, :second_gear do
420
- validates_presence_of :seatbelt_on
421
- end
422
- end
423
-
424
- def put_on_seatbelt
425
- ...
426
- end
427
-
428
- def benchmark
429
- ...
430
- yield
431
- ...
432
- end
433
- end
434
-
435
- class VehicleObserver
436
- include DataMapper::Observer
437
-
438
- observe Vehicle
439
-
440
- # Callback for :ignite event *before* the transition is performed
441
- before_transition :on => :ignite do |transition|
442
- # log message (self is the record)
443
- end
444
-
445
- # Generic transition callback *after* the transition is performed
446
- after_transition do |transition|
447
- Audit.log(self, transition) # self is the record
448
- end
449
-
450
- around_transition do |transition, block|
451
- # mark start time
452
- block.call
453
- # mark stop time
454
- end
455
-
456
- # Generic callback after the transition fails to perform
457
- after_transition_failure do |transition|
458
- Audit.log(self, transition) # self is the record
459
- end
460
- end
461
-
462
- *Note* that the DataMapper::Observer integration is optional and only available
463
- when the dm-observer library is installed.
464
-
465
- For more information about the various behaviors added for DataMapper state
466
- machines, see StateMachine::Integrations::DataMapper.
467
-
468
- === Mongoid
469
-
470
- The Mongoid integration adds support for automatically saving the record,
471
- basic scopes, validation errors, and observers. For example,
472
-
473
- class Vehicle
474
- include Mongoid::Document
475
-
476
- state_machine :initial => :parked do
477
- before_transition :parked => any - :parked, :do => :put_on_seatbelt
478
- after_transition any => :parked do |vehicle, transition|
479
- vehicle.seatbelt = 'off' # self is the record
480
- end
481
- around_transition :benchmark
482
-
483
- event :ignite do
484
- transition :parked => :idling
485
- end
486
-
487
- state :first_gear, :second_gear do
488
- validates_presence_of :seatbelt_on
489
- end
490
- end
491
-
492
- def put_on_seatbelt
493
- ...
494
- end
495
-
496
- def benchmark
497
- ...
498
- yield
499
- ...
500
- end
501
- end
502
-
503
- class VehicleObserver < Mongoid::Observer
504
- # Callback for :ignite event *before* the transition is performed
505
- def before_ignite(vehicle, transition)
506
- # log message
507
- end
508
-
509
- # Generic transition callback *after* the transition is performed
510
- def after_transition(vehicle, transition)
511
- Audit.log(vehicle, transition)
512
- end
513
- end
514
-
515
- For more information about the various behaviors added for Mongoid state
516
- machines, see StateMachine::Integrations::Mongoid.
517
-
518
- === MongoMapper
519
-
520
- The MongoMapper integration adds support for automatically saving the record,
521
- basic scopes, validation errors and callbacks. For example,
522
-
523
- class Vehicle
524
- include MongoMapper::Document
525
-
526
- state_machine :initial => :parked do
527
- before_transition :parked => any - :parked, :do => :put_on_seatbelt
528
- after_transition any => :parked do |vehicle, transition|
529
- vehicle.seatbelt = 'off' # self is the record
530
- end
531
- around_transition :benchmark
532
-
533
- event :ignite do
534
- transition :parked => :idling
535
- end
536
-
537
- state :first_gear, :second_gear do
538
- validates_presence_of :seatbelt_on
539
- end
540
- end
541
-
542
- def put_on_seatbelt
543
- ...
544
- end
545
-
546
- def benchmark
547
- ...
548
- yield
549
- ...
550
- end
551
- end
552
-
553
- For more information about the various behaviors added for MongoMapper state
554
- machines, see StateMachine::Integrations::MongoMapper.
555
-
556
- === Sequel
557
-
558
- Like the ActiveRecord integration, the Sequel integration adds support for
559
- database transactions, automatically saving the record, named scopes, validation
560
- errors and callbacks. For example,
561
-
562
- class Vehicle < Sequel::Model
563
- state_machine :initial => :parked do
564
- before_transition :parked => any - :parked, :do => :put_on_seatbelt
565
- after_transition any => :parked do |transition|
566
- self.seatbelt = 'off' # self is the record
567
- end
568
- around_transition :benchmark
569
-
570
- event :ignite do
571
- transition :parked => :idling
572
- end
573
-
574
- state :first_gear, :second_gear do
575
- validates_presence_of :seatbelt_on
576
- end
577
- end
578
-
579
- def put_on_seatbelt
580
- ...
581
- end
582
-
583
- def benchmark
584
- ...
585
- yield
586
- ...
587
- end
588
- end
589
-
590
- For more information about the various behaviors added for Sequel state
591
- machines, see StateMachine::Integrations::Sequel.
592
-
593
- == Syntax flexibility
594
-
595
- Although state_machine introduces a simplified syntax, it still remains
596
- backwards compatible with previous versions and other state-related libraries by
597
- providing some flexibility around how transitions are defined. See below for an
598
- overview of these syntaxes.
599
-
600
- === Verbose syntax
601
-
602
- In general, it's recommended that state machines use the implicit syntax for
603
- transitions. However, you can be a little more explicit and verbose about
604
- transitions by using the <tt>:from</tt>, <tt>:except_from</tt>, <tt>:to</tt>,
605
- and <tt>:except_to</tt> options.
606
-
607
- For example, transitions and callbacks can be defined like so:
608
-
609
- class Vehicle
610
- state_machine :initial => :parked do
611
- before_transition :from => :parked, :except_to => :parked, :do => :put_on_seatbelt
612
- after_transition :to => :parked do |transition|
613
- self.seatbelt = 'off' # self is the record
614
- end
615
-
616
- event :ignite do
617
- transition :from => :parked, :to => :idling
618
- end
619
- end
620
- end
621
-
622
- === Transition context
623
-
624
- Some flexibility is provided around the context in which transitions can be
625
- defined. In almost all examples throughout the documentation, transitions are
626
- defined within the context of an event. If you prefer to have state machines
627
- defined in the context of a *state* either out of preference or in order to
628
- easily migrate from a different library, you can do so as shown below:
629
-
630
- class Vehicle
631
- state_machine :initial => :parked do
632
- ...
633
-
634
- state :parked do
635
- transition :to => :idling, :on => [:ignite, :shift_up], :if => :seatbelt_on?
636
-
637
- def speed
638
- 0
639
- end
640
- end
641
-
642
- state :first_gear do
643
- transition :to => :second_gear, :on => :shift_up
644
-
645
- def speed
646
- 10
647
- end
648
- end
649
-
650
- state :idling, :first_gear do
651
- transition :to => :parked, :on => :park
652
- end
653
- end
654
- end
655
-
656
- In the above example, there's no need to specify the +from+ state for each
657
- transition since it's inferred from the context.
658
-
659
- You can also define transitions completely outside the context of a particular
660
- state / event. This may be useful in cases where you're building a state
661
- machine from a data store instead of part of the class definition. See the
662
- example below:
663
-
664
- class Vehicle
665
- state_machine :initial => :parked do
666
- ...
667
-
668
- transition :parked => :idling, :on => [:ignite, :shift_up]
669
- transition :first_gear => :second_gear, :second_gear => :third_gear, :on => :shift_up
670
- transition [:idling, :first_gear] => :parked, :on => :park
671
- transition [:idling, :first_gear] => :parked, :on => :park
672
- transition all - [:parked, :stalled] => :stalled, :unless => :auto_shop_busy?
673
- end
674
- end
675
-
676
- Notice that in these alternative syntaxes:
677
- * You can continue to configure <tt>:if</tt> and <tt>:unless</tt> conditions
678
- * You can continue to define +from+ states (when in the machine context) using
679
- the +all+, +any+, and +same+ helper methods
680
-
681
- == Tools
682
-
683
- === Generating graphs
684
-
685
- This library comes with built-in support for generating di-graphs based on the
686
- events, states, and transitions defined for a state machine using GraphViz[http://www.graphviz.org].
687
- This requires that both the <tt>ruby-graphviz</tt> gem and graphviz library be
688
- installed on the system.
689
-
690
- ==== Examples
691
-
692
- To generate a graph for a specific file / class:
693
-
694
- rake state_machine:draw FILE=vehicle.rb CLASS=Vehicle
695
-
696
- To save files to a specific path:
697
-
698
- rake state_machine:draw FILE=vehicle.rb CLASS=Vehicle TARGET=files
699
-
700
- To customize the image format / orientation:
701
-
702
- rake state_machine:draw FILE=vehicle.rb CLASS=Vehicle FORMAT=jpg ORIENTATION=landscape
703
-
704
- To generate multiple state machine graphs:
705
-
706
- rake state_machine:draw FILE=vehicle.rb,car.rb CLASS=Vehicle,Car
707
-
708
- *Note* that this will generate a different file for every state machine defined
709
- in the class. The generated files will use an output filename of the format
710
- #{class_name}_#{machine_name}.#{format}.
711
-
712
- For examples of actual images generated using this task, see those under the
713
- examples folder.
714
-
715
- === Interactive graphs
716
-
717
- Jean Bovet's {Visual Automata Simulator}[http://www.cs.usfca.edu/~jbovet/vas.html]
718
- is a great tool for "simulating, visualizing and transforming finite state
719
- automata and Turing Machines". It can help in the creation of states and events
720
- for your models. It is cross-platform, written in Java.
721
-
722
- == Web Frameworks
723
-
724
- === Ruby on Rails
725
-
726
- Integrating state_machine into your Ruby on Rails application is straightforward
727
- and provides a few additional features specific to the framework. To get
728
- started, following the steps below.
729
-
730
- ==== 1. Install the gem
731
-
732
- If using Rails 2.x:
733
-
734
- # In config/environment.rb
735
- ...
736
- Rails::Initializer.run do |config|
737
- ...
738
- config.gem 'state_machine', :version => '~> 1.0'
739
- ...
740
- end
741
-
742
- If using Rails 3.x or up:
743
-
744
- # In Gemfile
745
- ...
746
- gem 'state_machine'
747
- gem 'ruby-graphviz', :require => 'graphviz' # Optional: only required for graphing
748
-
749
- As usual, run <tt>bundle install</tt> to load the gems.
750
-
751
- ==== 2. Create a model
752
-
753
- Create a model with a field to store the state, along with other any other
754
- fields your application requires:
755
-
756
- $ rails generate model Vehicle state:string
757
- $ rake db:migrate
758
-
759
- ==== 3. Configure the state machine
760
-
761
- Add the state machine to your model. Following the examples above,
762
- +app/models/vehicle.rb+ might become:
763
-
764
- class Vehicle < ActiveRecord::Base
765
- state_machine :initial => :parked do
766
- before_transition :parked => any - :parked, :do => :put_on_seatbelt
767
- ...
768
- end
769
- end
770
-
771
- ==== Rake tasks
772
-
773
- There is a special integration Rake task for generating state machines for
774
- classes used in a Ruby on Rails application. This task will load the application
775
- environment, meaning that it's unnecessary to specify the actual file to load.
776
-
777
- For example,
778
-
779
- rake state_machine:draw CLASS=Vehicle
780
-
781
- If you are using this library as a gem in Rails 2.x, the following must be added
782
- to the end of your application's Rakefile in order for the above task to work:
783
-
784
- require 'tasks/state_machine'
785
-
786
- === Merb
787
-
788
- ==== Rake tasks
789
-
790
- Like Ruby on Rails, there is a special integration Rake task for generating
791
- state machines for classes used in a Merb application. This task will load the
792
- application environment, meaning that it's unnecessary to specify the actual
793
- files to load.
794
-
795
- For example,
796
-
797
- rake state_machine:draw CLASS=Vehicle
798
-
799
- == Testing
800
-
801
- To run the core test suite (does *not* test any of the integrations):
802
-
803
- bundle install
804
- bundle exec rake test
805
-
806
- To run integration tests:
807
-
808
- bundle install
809
- rake appraisal:install
810
- rake appraisal:test
811
-
812
- You can also test a specific version:
813
-
814
- rake appraisal:active_model-3.0.0 test
815
- rake appraisal:active_record-2.0.0 test
816
- rake appraisal:data_mapper-0.9.4 test
817
- rake appraisal:mongoid-2.0.0 test
818
- rake appraisal:mongo_mapper-0.5.5 test
819
- rake appraisal:sequel-2.8.0 test
820
-
821
- == Caveats
822
-
823
- The following caveats should be noted when using state_machine:
824
-
825
- * DataMapper: Attribute-based event transitions are disabled when dm-validations 0.9.4 - 0.9.6 is in use
826
- * Overridden event methods won't get invoked when using attribute-based event transitions
827
- * around_transition callbacks in ORM integrations won't work on JRuby since it doesn't support continuations
828
-
829
- == Dependencies
830
-
831
- * Ruby 1.8.6 or later
832
-
833
- If using specific integrations:
834
-
835
- * ActiveModel[http://rubyonrails.org] integration: 3.0.0 or later
836
- * ActiveRecord[http://rubyonrails.org] integration: 2.0.0 or later
837
- * DataMapper[http://datamapper.org] integration: 0.9.4 or later
838
- * Mongoid[http://mongoid.org] integration: 2.0.0 or later
839
- * MongoMapper[http://mongomapper.com] integration: 0.5.5 or later
840
- * Sequel[http://sequel.rubyforge.org] integration: 2.8.0 or later
841
-
842
- If graphing state machine:
843
-
844
- * ruby-graphviz[http://github.com/glejeune/Ruby-Graphviz]: 0.9.0 or later