state_machine 1.0.2 → 1.0.3

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 (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