state_machine 0.6.3 → 0.7.0

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 (56) hide show
  1. data/CHANGELOG.rdoc +31 -1
  2. data/README.rdoc +33 -21
  3. data/Rakefile +2 -2
  4. data/examples/merb-rest/controller.rb +51 -0
  5. data/examples/merb-rest/model.rb +28 -0
  6. data/examples/merb-rest/view_edit.html.erb +24 -0
  7. data/examples/merb-rest/view_index.html.erb +23 -0
  8. data/examples/merb-rest/view_new.html.erb +13 -0
  9. data/examples/merb-rest/view_show.html.erb +17 -0
  10. data/examples/rails-rest/controller.rb +43 -0
  11. data/examples/rails-rest/migration.rb +11 -0
  12. data/examples/rails-rest/model.rb +23 -0
  13. data/examples/rails-rest/view_edit.html.erb +25 -0
  14. data/examples/rails-rest/view_index.html.erb +23 -0
  15. data/examples/rails-rest/view_new.html.erb +14 -0
  16. data/examples/rails-rest/view_show.html.erb +17 -0
  17. data/lib/state_machine/assertions.rb +2 -2
  18. data/lib/state_machine/callback.rb +14 -8
  19. data/lib/state_machine/condition_proxy.rb +3 -3
  20. data/lib/state_machine/event.rb +19 -21
  21. data/lib/state_machine/event_collection.rb +114 -0
  22. data/lib/state_machine/extensions.rb +127 -11
  23. data/lib/state_machine/guard.rb +1 -1
  24. data/lib/state_machine/integrations/active_record/locale.rb +2 -1
  25. data/lib/state_machine/integrations/active_record.rb +117 -39
  26. data/lib/state_machine/integrations/data_mapper/observer.rb +20 -64
  27. data/lib/state_machine/integrations/data_mapper.rb +71 -26
  28. data/lib/state_machine/integrations/sequel.rb +69 -21
  29. data/lib/state_machine/machine.rb +267 -139
  30. data/lib/state_machine/machine_collection.rb +145 -0
  31. data/lib/state_machine/matcher.rb +2 -2
  32. data/lib/state_machine/node_collection.rb +9 -4
  33. data/lib/state_machine/state.rb +22 -32
  34. data/lib/state_machine/state_collection.rb +66 -17
  35. data/lib/state_machine/transition.rb +259 -28
  36. data/lib/state_machine.rb +121 -56
  37. data/tasks/state_machine.rake +1 -0
  38. data/tasks/state_machine.rb +26 -0
  39. data/test/active_record.log +116877 -0
  40. data/test/functional/state_machine_test.rb +118 -12
  41. data/test/sequel.log +28542 -0
  42. data/test/unit/callback_test.rb +46 -1
  43. data/test/unit/condition_proxy_test.rb +55 -28
  44. data/test/unit/event_collection_test.rb +228 -0
  45. data/test/unit/event_test.rb +51 -46
  46. data/test/unit/integrations/active_record_test.rb +128 -70
  47. data/test/unit/integrations/data_mapper_test.rb +150 -58
  48. data/test/unit/integrations/sequel_test.rb +63 -6
  49. data/test/unit/invalid_event_test.rb +7 -0
  50. data/test/unit/machine_collection_test.rb +678 -0
  51. data/test/unit/machine_test.rb +198 -91
  52. data/test/unit/node_collection_test.rb +33 -30
  53. data/test/unit/state_collection_test.rb +112 -5
  54. data/test/unit/state_test.rb +23 -3
  55. data/test/unit/transition_test.rb +750 -89
  56. metadata +28 -3
@@ -3,7 +3,7 @@ require File.expand_path(File.dirname(__FILE__) + '/../test_helper')
3
3
  class CallbackTest < Test::Unit::TestCase
4
4
  def test_should_raise_exception_if_do_option_not_specified
5
5
  exception = assert_raise(ArgumentError) { StateMachine::Callback.new }
6
- assert_match ':do callback must be specified', exception.message
6
+ assert_equal ':do callback must be specified', exception.message
7
7
  end
8
8
 
9
9
  def test_should_not_raise_exception_if_do_option_specified
@@ -17,6 +17,10 @@ class CallbackTest < Test::Unit::TestCase
17
17
  def test_should_not_bind_to_objects
18
18
  assert !StateMachine::Callback.bind_to_object
19
19
  end
20
+
21
+ def test_should_not_have_a_terminator
22
+ assert_nil StateMachine::Callback.terminator
23
+ end
20
24
  end
21
25
 
22
26
  class CallbackByDefaultTest < Test::Unit::TestCase
@@ -236,3 +240,44 @@ class CallbackWithBoundObjectTest < Test::Unit::TestCase
236
240
  assert_equal [1, 2, 3], @callback.call(@object)
237
241
  end
238
242
  end
243
+
244
+ class CallbackWithApplicationBoundObjectTest < Test::Unit::TestCase
245
+ def setup
246
+ @original_bind_to_object = StateMachine::Callback.bind_to_object
247
+ StateMachine::Callback.bind_to_object = true
248
+
249
+ @object = Object.new
250
+ @callback = StateMachine::Callback.new(:do => lambda {|*args| self})
251
+ end
252
+
253
+ def test_should_call_method_within_the_context_of_the_object
254
+ assert_equal @object, @callback.call(@object)
255
+ end
256
+
257
+ def teardown
258
+ StateMachine::Callback.bind_to_object = @original_bind_to_object
259
+ end
260
+ end
261
+
262
+ class CallbackWithApplicationTerminatorTest < Test::Unit::TestCase
263
+ def setup
264
+ @original_terminator = StateMachine::Callback.bind_to_object
265
+ StateMachine::Callback.terminator = lambda {|result| result == false}
266
+
267
+ @object = Object.new
268
+ end
269
+
270
+ def test_should_not_halt_if_terminator_does_not_match
271
+ callback = StateMachine::Callback.new(:do => lambda {true})
272
+ assert_nothing_thrown { callback.call(@object) }
273
+ end
274
+
275
+ def test_should_halt_if_terminator_matches
276
+ callback = StateMachine::Callback.new(:do => lambda {false})
277
+ assert_throws(:halt) { callback.call(@object) }
278
+ end
279
+
280
+ def teardown
281
+ StateMachine::Callback.bind_to_object = @original_bind_to_object
282
+ end
283
+ end
@@ -31,10 +31,10 @@ class ConditionProxyTest < Test::Unit::TestCase
31
31
  def test_should_pass_object_into_proxy_condition
32
32
  condition_args = []
33
33
  condition_proxy = StateMachine::ConditionProxy.new(Validateable, lambda {|*args| condition_args = args})
34
- validation = condition_proxy.validate(:name)
34
+ options = condition_proxy.validate[0]
35
35
 
36
36
  object = Validateable.new
37
- validation.last[:if].call(object)
37
+ options[:if].call(object)
38
38
 
39
39
  assert_equal [object], condition_args
40
40
  end
@@ -49,10 +49,10 @@ class ConditionProxyTest < Test::Unit::TestCase
49
49
  end
50
50
 
51
51
  condition_proxy = StateMachine::ConditionProxy.new(klass, :callback)
52
- validation = condition_proxy.validate(:name)
52
+ options = condition_proxy.validate[0]
53
53
 
54
54
  object = klass.new
55
- validation.last[:if].call(object)
55
+ options[:if].call(object)
56
56
 
57
57
  assert object.callback_called
58
58
  end
@@ -63,10 +63,10 @@ class ConditionProxyTest < Test::Unit::TestCase
63
63
  end
64
64
 
65
65
  condition_proxy = StateMachine::ConditionProxy.new(klass, '@callback_called = true')
66
- validation = condition_proxy.validate(:name)
66
+ options = condition_proxy.validate[0]
67
67
 
68
68
  object = klass.new
69
- validation.last[:if].call(object)
69
+ options[:if].call(object)
70
70
 
71
71
  assert object.callback_called
72
72
  end
@@ -78,8 +78,7 @@ class ConditionProxyWithoutConditionsTest < Test::Unit::TestCase
78
78
  condition_proxy = StateMachine::ConditionProxy.new(Validateable, lambda {@proxy_result})
79
79
 
80
80
  @object = Validateable.new
81
- @validation = condition_proxy.validate(:name)
82
- @options = @validation.last
81
+ @options = condition_proxy.validate[0]
83
82
  end
84
83
 
85
84
  def test_should_have_options_configuration
@@ -114,8 +113,7 @@ class ConditionProxyWithIfConditionTest < Test::Unit::TestCase
114
113
  @object = Validateable.new
115
114
 
116
115
  @condition_result = nil
117
- @validation = condition_proxy.validate(:name, :if => lambda {@condition_result})
118
- @options = @validation.pop
116
+ @options = condition_proxy.validate(:if => lambda {@condition_result})[0]
119
117
  end
120
118
 
121
119
  def test_should_have_if_option
@@ -144,8 +142,7 @@ class ConditionProxyWithIfConditionTest < Test::Unit::TestCase
144
142
  end
145
143
 
146
144
  condition_proxy = StateMachine::ConditionProxy.new(klass, lambda {true})
147
- validation = condition_proxy.validate(:name, :if => :callback)
148
- options = validation.last
145
+ options = condition_proxy.validate(:if => :callback)[0]
149
146
 
150
147
  object = klass.new
151
148
  object.callback = false
@@ -161,8 +158,7 @@ class ConditionProxyWithIfConditionTest < Test::Unit::TestCase
161
158
  end
162
159
 
163
160
  condition_proxy = StateMachine::ConditionProxy.new(klass, lambda {true})
164
- validation = condition_proxy.validate(:name, :if => '@callback')
165
- options = validation.last
161
+ options = condition_proxy.validate(:if => '@callback')[0]
166
162
 
167
163
  object = klass.new
168
164
  object.callback = false
@@ -175,15 +171,13 @@ end
175
171
 
176
172
  class ConditionProxyWithMultipleIfConditionsTest < Test::Unit::TestCase
177
173
  def setup
178
- @proxy_result = true
179
- condition_proxy = StateMachine::ConditionProxy.new(Validateable, lambda {@proxy_result})
174
+ condition_proxy = StateMachine::ConditionProxy.new(Validateable, lambda {true})
180
175
 
181
176
  @object = Validateable.new
182
177
 
183
178
  @first_condition_result = nil
184
179
  @second_condition_result = nil
185
- @validation = condition_proxy.validate(:name, :if => [lambda {@first_condition_result}, lambda {@second_condition_result}])
186
- @options = @validation.pop
180
+ @options = condition_proxy.validate(:if => [lambda {@first_condition_result}, lambda {@second_condition_result}])[0]
187
181
  end
188
182
 
189
183
  def test_should_be_true_if_all_conditions_are_true
@@ -211,8 +205,7 @@ class ConditionProxyWithUnlessConditionTest < Test::Unit::TestCase
211
205
  @object = Validateable.new
212
206
 
213
207
  @condition_result = nil
214
- @validation = condition_proxy.validate(:name, :unless => lambda {@condition_result})
215
- @options = @validation.pop
208
+ @options = condition_proxy.validate(:unless => lambda {@condition_result})[0]
216
209
  end
217
210
 
218
211
  def test_should_have_if_option
@@ -241,8 +234,7 @@ class ConditionProxyWithUnlessConditionTest < Test::Unit::TestCase
241
234
  end
242
235
 
243
236
  condition_proxy = StateMachine::ConditionProxy.new(klass, lambda {true})
244
- validation = condition_proxy.validate(:name, :unless => :callback)
245
- options = validation.last
237
+ options = condition_proxy.validate(:unless => :callback)[0]
246
238
 
247
239
  object = klass.new
248
240
  object.callback = true
@@ -258,8 +250,7 @@ class ConditionProxyWithUnlessConditionTest < Test::Unit::TestCase
258
250
  end
259
251
 
260
252
  condition_proxy = StateMachine::ConditionProxy.new(klass, lambda {true})
261
- validation = condition_proxy.validate(:name, :unless => '@callback')
262
- options = validation.last
253
+ options = condition_proxy.validate(:unless => '@callback')[0]
263
254
 
264
255
  object = klass.new
265
256
  object.callback = true
@@ -272,15 +263,13 @@ end
272
263
 
273
264
  class ConditionProxyWithMultipleUnlessConditionsTest < Test::Unit::TestCase
274
265
  def setup
275
- @proxy_result = true
276
- condition_proxy = StateMachine::ConditionProxy.new(Validateable, lambda {@proxy_result})
266
+ condition_proxy = StateMachine::ConditionProxy.new(Validateable, lambda {true})
277
267
 
278
268
  @object = Validateable.new
279
269
 
280
270
  @first_condition_result = nil
281
271
  @second_condition_result = nil
282
- @validation = condition_proxy.validate(:name, :unless => [lambda {@first_condition_result}, lambda {@second_condition_result}])
283
- @options = @validation.pop
272
+ @options = condition_proxy.validate(:unless => [lambda {@first_condition_result}, lambda {@second_condition_result}])[0]
284
273
  end
285
274
 
286
275
  def test_should_be_true_if_all_conditions_are_false
@@ -299,3 +288,41 @@ class ConditionProxyWithMultipleUnlessConditionsTest < Test::Unit::TestCase
299
288
  assert !@options[:if].call(@object)
300
289
  end
301
290
  end
291
+
292
+ class ConditionProxyWithIfAndUnlessConditionsTest < Test::Unit::TestCase
293
+ def setup
294
+ condition_proxy = StateMachine::ConditionProxy.new(Validateable, lambda {true})
295
+
296
+ @object = Validateable.new
297
+
298
+ @if_condition_result = nil
299
+ @unless_condition_result = nil
300
+ @options = condition_proxy.validate(:if => lambda {@if_condition_result}, :unless => lambda {@unless_condition_result})[0]
301
+ end
302
+
303
+ def test_should_be_false_if_if_condition_is_false
304
+ @if_condition_result = false
305
+ @unless_condition_result = false
306
+ assert !@options[:if].call(@object)
307
+
308
+ @if_condition_result = false
309
+ @unless_condition_result = true
310
+ assert !@options[:if].call(@object)
311
+ end
312
+
313
+ def test_should_be_false_if_unless_condition_is_true
314
+ @if_condition_result = false
315
+ @unless_condition_result = true
316
+ assert !@options[:if].call(@object)
317
+
318
+ @if_condition_result = true
319
+ @unless_condition_result = true
320
+ assert !@options[:if].call(@object)
321
+ end
322
+
323
+ def test_should_be_true_if_if_condition_is_true_and_unless_condition_is_false
324
+ @if_condition_result = true
325
+ @unless_condition_result = false
326
+ assert @options[:if].call(@object)
327
+ end
328
+ end
@@ -0,0 +1,228 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../test_helper')
2
+
3
+ class EventCollectionByDefaultTest < Test::Unit::TestCase
4
+ def setup
5
+ @machine = StateMachine::Machine.new(Class.new)
6
+ @events = StateMachine::EventCollection.new(@machine)
7
+ end
8
+
9
+ def test_should_not_have_any_nodes
10
+ assert_equal 0, @events.length
11
+ end
12
+
13
+ def test_should_have_a_machine
14
+ assert_equal @machine, @events.machine
15
+ end
16
+
17
+ def test_should_not_have_any_valid_events_for_an_object
18
+ assert @events.valid_for(@object).empty?
19
+ end
20
+
21
+ def test_should_not_have_any_transitions_for_an_object
22
+ assert @events.transitions_for(@object).empty?
23
+ end
24
+ end
25
+
26
+ class EventCollectionTest < Test::Unit::TestCase
27
+ def setup
28
+ machine = StateMachine::Machine.new(Class.new, :namespace => 'alarm')
29
+ @events = StateMachine::EventCollection.new(machine)
30
+
31
+ @events << @open = StateMachine::Event.new(machine, :enable)
32
+ end
33
+
34
+ def test_should_index_by_name
35
+ assert_equal @open, @events[:enable, :name]
36
+ end
37
+
38
+ def test_should_index_by_name_by_default
39
+ assert_equal @open, @events[:enable]
40
+ end
41
+
42
+ def test_should_index_by_qualified_name
43
+ assert_equal @open, @events[:enable_alarm, :qualified_name]
44
+ end
45
+ end
46
+
47
+ class EventCollectionWithEventsWithTransitionsTest < Test::Unit::TestCase
48
+ def setup
49
+ @klass = Class.new
50
+ @machine = StateMachine::Machine.new(@klass, :initial => :parked)
51
+ @events = StateMachine::EventCollection.new(@machine)
52
+
53
+ @machine.state :idling, :stalled
54
+ @machine.event :ignite
55
+
56
+ @events << @ignite = StateMachine::Event.new(@machine, :ignite)
57
+ @ignite.transition :parked => :idling
58
+ @ignite.transition :stalled => :idling
59
+ end
60
+
61
+ def test_should_only_include_valid_events_for_an_object
62
+ object = @klass.new
63
+ object.state = 'parked'
64
+ assert_equal [@ignite], @events.valid_for(object)
65
+
66
+ object.state = 'stalled'
67
+ assert_equal [@ignite], @events.valid_for(object)
68
+
69
+ object.state = 'idling'
70
+ assert_equal [], @events.valid_for(object)
71
+ end
72
+
73
+ def test_should_only_include_valid_transitions_for_an_object
74
+ object = @klass.new
75
+ object.state = 'parked'
76
+ assert_equal [{:object => object, :attribute => :state, :event => :ignite, :from => 'parked', :to => 'idling'}], @events.transitions_for(object).map {|transition| transition.attributes}
77
+
78
+ object.state = 'stalled'
79
+ assert_equal [{:object => object, :attribute => :state, :event => :ignite, :from => 'stalled', :to => 'idling'}], @events.transitions_for(object).map {|transition| transition.attributes}
80
+
81
+ object.state = 'idling'
82
+ assert_equal [], @events.transitions_for(object)
83
+ end
84
+ end
85
+
86
+ class EventCollectionWithMultipleEventsTest < Test::Unit::TestCase
87
+ def setup
88
+ @klass = Class.new
89
+ @machine = StateMachine::Machine.new(@klass, :initial => :parked)
90
+ @events = StateMachine::EventCollection.new(@machine)
91
+
92
+ @machine.state :first_gear
93
+ @machine.event :park, :shift_down
94
+
95
+ @events << @park = StateMachine::Event.new(@machine, :park)
96
+ @park.transition :first_gear => :parked
97
+
98
+ @events << @shift_down = StateMachine::Event.new(@machine, :shift_down)
99
+ @shift_down.transition :first_gear => :parked
100
+ end
101
+
102
+ def test_should_only_include_all_valid_events_for_an_object
103
+ object = @klass.new
104
+ object.state = 'first_gear'
105
+ assert_equal [@park, @shift_down], @events.valid_for(object)
106
+ end
107
+ end
108
+
109
+ class EventCollectionWithoutMachineActionTest < Test::Unit::TestCase
110
+ def setup
111
+ @klass = Class.new
112
+ @machine = StateMachine::Machine.new(@klass, :initial => :parked)
113
+ @events = StateMachine::EventCollection.new(@machine)
114
+
115
+ @machine.event :ignite
116
+ @events << StateMachine::Event.new(@machine, :ignite)
117
+
118
+ @object = @klass.new
119
+ end
120
+
121
+ def test_should_not_have_an_attribute_transition
122
+ assert_nil @events.attribute_transition_for(@object)
123
+ end
124
+ end
125
+
126
+ class EventCollectionAttributeWithMachineActionTest < Test::Unit::TestCase
127
+ def setup
128
+ @klass = Class.new do
129
+ def save
130
+ end
131
+ end
132
+
133
+ @machine = StateMachine::Machine.new(@klass, :initial => :parked, :action => :save)
134
+ @events = StateMachine::EventCollection.new(@machine)
135
+
136
+ @machine.event :ignite
137
+ @machine.state :parked, :idling
138
+ @events << @ignite = StateMachine::Event.new(@machine, :ignite)
139
+
140
+ @object = @klass.new
141
+ end
142
+
143
+ def test_should_not_have_transition_if_nil
144
+ @object.state_event = nil
145
+ assert_nil @events.attribute_transition_for(@object)
146
+ end
147
+
148
+ def test_should_not_have_transition_if_empty
149
+ @object.state_event = ''
150
+ assert_nil @events.attribute_transition_for(@object)
151
+ end
152
+
153
+ def test_should_have_invalid_transition_if_invalid_event_specified
154
+ @object.state_event = 'invalid'
155
+ assert_equal false, @events.attribute_transition_for(@object)
156
+ end
157
+
158
+ def test_should_have_invalid_transition_if_event_cannot_be_fired
159
+ @object.state_event = 'ignite'
160
+ assert_equal false, @events.attribute_transition_for(@object)
161
+ end
162
+
163
+ def test_should_have_valid_transition_if_event_can_be_fired
164
+ @ignite.transition :parked => :idling
165
+ @object.state_event = 'ignite'
166
+
167
+ assert_instance_of StateMachine::Transition, @events.attribute_transition_for(@object)
168
+ end
169
+ end
170
+
171
+ class EventCollectionWithValidationsTest < Test::Unit::TestCase
172
+ def setup
173
+ StateMachine::Integrations.const_set('Custom', Module.new do
174
+ def invalidate(object, attribute, message, values = [])
175
+ (object.errors ||= []) << generate_message(message, values)
176
+ end
177
+
178
+ def reset(object)
179
+ object.errors = []
180
+ end
181
+ end)
182
+
183
+ @klass = Class.new do
184
+ attr_accessor :errors
185
+
186
+ def initialize
187
+ @errors = []
188
+ super
189
+ end
190
+ end
191
+
192
+ @machine = StateMachine::Machine.new(@klass, :initial => :parked, :action => :save, :integration => :custom)
193
+ @events = StateMachine::EventCollection.new(@machine)
194
+
195
+ @machine.event :ignite
196
+ @machine.state :parked, :idling
197
+ @events << @ignite = StateMachine::Event.new(@machine, :ignite)
198
+
199
+ @object = @klass.new
200
+ end
201
+
202
+ def test_should_invalidate_if_invalid_event_specified
203
+ @object.state_event = 'invalid'
204
+ @events.attribute_transition_for(@object, true)
205
+
206
+ assert_equal ['is invalid'], @object.errors
207
+ end
208
+
209
+ def test_should_invalidate_if_event_cannot_be_fired
210
+ @object.state = 'idling'
211
+ @object.state_event = 'ignite'
212
+ @events.attribute_transition_for(@object, true)
213
+
214
+ assert_equal ['cannot transition when idling'], @object.errors
215
+ end
216
+
217
+ def test_should_not_invalidate_event_can_be_fired
218
+ @ignite.transition :parked => :idling
219
+ @object.state_event = 'ignite'
220
+ @events.attribute_transition_for(@object, true)
221
+
222
+ assert_equal [], @object.errors
223
+ end
224
+
225
+ def teardown
226
+ StateMachine::Integrations.send(:remove_const, 'Custom')
227
+ end
228
+ end