state_machine 0.9.4 → 0.10.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 (68) hide show
  1. data/CHANGELOG.rdoc +20 -0
  2. data/LICENSE +1 -1
  3. data/README.rdoc +74 -4
  4. data/Rakefile +3 -3
  5. data/lib/state_machine.rb +51 -24
  6. data/lib/state_machine/{guard.rb → branch.rb} +34 -40
  7. data/lib/state_machine/callback.rb +13 -18
  8. data/lib/state_machine/error.rb +13 -0
  9. data/lib/state_machine/eval_helpers.rb +3 -0
  10. data/lib/state_machine/event.rb +67 -30
  11. data/lib/state_machine/event_collection.rb +20 -3
  12. data/lib/state_machine/extensions.rb +3 -3
  13. data/lib/state_machine/integrations.rb +7 -0
  14. data/lib/state_machine/integrations/active_model.rb +149 -59
  15. data/lib/state_machine/integrations/active_model/versions.rb +30 -0
  16. data/lib/state_machine/integrations/active_record.rb +74 -148
  17. data/lib/state_machine/integrations/active_record/locale.rb +0 -7
  18. data/lib/state_machine/integrations/active_record/versions.rb +149 -0
  19. data/lib/state_machine/integrations/base.rb +64 -0
  20. data/lib/state_machine/integrations/data_mapper.rb +50 -39
  21. data/lib/state_machine/integrations/data_mapper/observer.rb +47 -12
  22. data/lib/state_machine/integrations/data_mapper/versions.rb +62 -0
  23. data/lib/state_machine/integrations/mongo_mapper.rb +37 -64
  24. data/lib/state_machine/integrations/mongo_mapper/locale.rb +4 -0
  25. data/lib/state_machine/integrations/mongo_mapper/versions.rb +102 -0
  26. data/lib/state_machine/integrations/mongoid.rb +297 -0
  27. data/lib/state_machine/integrations/mongoid/locale.rb +4 -0
  28. data/lib/state_machine/integrations/mongoid/versions.rb +18 -0
  29. data/lib/state_machine/integrations/sequel.rb +99 -55
  30. data/lib/state_machine/integrations/sequel/versions.rb +40 -0
  31. data/lib/state_machine/machine.rb +273 -136
  32. data/lib/state_machine/machine_collection.rb +21 -13
  33. data/lib/state_machine/node_collection.rb +6 -1
  34. data/lib/state_machine/path.rb +120 -0
  35. data/lib/state_machine/path_collection.rb +90 -0
  36. data/lib/state_machine/state.rb +28 -9
  37. data/lib/state_machine/state_collection.rb +1 -1
  38. data/lib/state_machine/transition.rb +65 -6
  39. data/lib/state_machine/transition_collection.rb +1 -1
  40. data/test/files/en.yml +8 -0
  41. data/test/functional/state_machine_test.rb +15 -2
  42. data/test/unit/branch_test.rb +890 -0
  43. data/test/unit/callback_test.rb +9 -36
  44. data/test/unit/error_test.rb +43 -0
  45. data/test/unit/event_collection_test.rb +67 -33
  46. data/test/unit/event_test.rb +165 -38
  47. data/test/unit/integrations/active_model_test.rb +103 -3
  48. data/test/unit/integrations/active_record_test.rb +90 -43
  49. data/test/unit/integrations/base_test.rb +87 -0
  50. data/test/unit/integrations/data_mapper_test.rb +105 -44
  51. data/test/unit/integrations/mongo_mapper_test.rb +261 -64
  52. data/test/unit/integrations/mongoid_test.rb +1529 -0
  53. data/test/unit/integrations/sequel_test.rb +33 -49
  54. data/test/unit/integrations_test.rb +4 -0
  55. data/test/unit/invalid_event_test.rb +15 -2
  56. data/test/unit/invalid_parallel_transition_test.rb +18 -0
  57. data/test/unit/invalid_transition_test.rb +72 -2
  58. data/test/unit/machine_collection_test.rb +55 -61
  59. data/test/unit/machine_test.rb +388 -26
  60. data/test/unit/node_collection_test.rb +14 -4
  61. data/test/unit/path_collection_test.rb +266 -0
  62. data/test/unit/path_test.rb +485 -0
  63. data/test/unit/state_collection_test.rb +30 -0
  64. data/test/unit/state_test.rb +82 -35
  65. data/test/unit/transition_collection_test.rb +48 -44
  66. data/test/unit/transition_test.rb +198 -41
  67. metadata +111 -74
  68. data/test/unit/guard_test.rb +0 -909
@@ -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_invalid_type_specified
5
5
  exception = assert_raise(ArgumentError) { StateMachine::Callback.new(:invalid) {} }
6
- assert_equal 'Type must be :before, :after, or :around', exception.message
6
+ assert_equal 'Type must be :before, :after, :around, or :failure', exception.message
7
7
  end
8
8
 
9
9
  def test_should_not_raise_exception_if_using_before_type
@@ -18,6 +18,10 @@ class CallbackTest < Test::Unit::TestCase
18
18
  assert_nothing_raised { StateMachine::Callback.new(:around) {} }
19
19
  end
20
20
 
21
+ def test_should_not_raise_exception_if_using_failure_type
22
+ assert_nothing_raised { StateMachine::Callback.new(:failure) {} }
23
+ end
24
+
21
25
  def test_should_raise_exception_if_no_methods_specified
22
26
  exception = assert_raise(ArgumentError) { StateMachine::Callback.new(:before) }
23
27
  assert_equal 'Method(s) for callback must be specified', exception.message
@@ -61,10 +65,10 @@ class CallbackByDefaultTest < Test::Unit::TestCase
61
65
  assert_nil @callback.terminator
62
66
  end
63
67
 
64
- def test_should_have_a_guard_with_all_matcher_requirements
65
- assert_equal StateMachine::AllMatcher.instance, @callback.guard.event_requirement
66
- assert_equal StateMachine::AllMatcher.instance, @callback.guard.state_requirements.first[:from]
67
- assert_equal StateMachine::AllMatcher.instance, @callback.guard.state_requirements.first[:to]
68
+ def test_should_have_a_branch_with_all_matcher_requirements
69
+ assert_equal StateMachine::AllMatcher.instance, @callback.branch.event_requirement
70
+ assert_equal StateMachine::AllMatcher.instance, @callback.branch.state_requirements.first[:from]
71
+ assert_equal StateMachine::AllMatcher.instance, @callback.branch.state_requirements.first[:to]
68
72
  end
69
73
 
70
74
  def test_should_not_have_any_known_states
@@ -695,34 +699,3 @@ class CallbackWithAroundTypeAndBoundMethodTest < Test::Unit::TestCase
695
699
  assert_equal [1, 2, 3], context
696
700
  end
697
701
  end
698
-
699
- class CallbackSuccessMatcherTest < Test::Unit::TestCase
700
- def setup
701
- @object = Object.new
702
- end
703
-
704
- def test_should_match_if_not_specified
705
- callback = StateMachine::Callback.new(:before) {}
706
- assert callback.matches_success?(true)
707
- end
708
-
709
- def test_should_match_if_true_and_not_including_failures
710
- callback = StateMachine::Callback.new(:before, :include_failures => false) {}
711
- assert callback.matches_success?(true)
712
- end
713
-
714
- def test_should_match_if_true_and_including_failures
715
- callback = StateMachine::Callback.new(:before, :include_failures => true) {}
716
- assert callback.matches_success?(true)
717
- end
718
-
719
- def test_should_not_match_if_false_and_not_including_failures
720
- callback = StateMachine::Callback.new(:before, :include_failures => false) {}
721
- assert !callback.matches_success?(false)
722
- end
723
-
724
- def test_Should_match_if_false_and_including_failures
725
- callback = StateMachine::Callback.new(:before, :include_failures => true) {}
726
- assert callback.matches_success?(false)
727
- end
728
- end
@@ -0,0 +1,43 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../test_helper')
2
+
3
+ class ErrorByDefaultTest < Test::Unit::TestCase
4
+ def setup
5
+ @machine = StateMachine::Machine.new(Class.new)
6
+ @collection = StateMachine::NodeCollection.new(@machine)
7
+ end
8
+
9
+ def test_should_not_have_any_nodes
10
+ assert_equal 0, @collection.length
11
+ end
12
+
13
+ def test_should_have_a_machine
14
+ assert_equal @machine, @collection.machine
15
+ end
16
+
17
+ def test_should_index_by_name
18
+ @collection << object = Struct.new(:name).new(:parked)
19
+ assert_equal object, @collection[:parked]
20
+ end
21
+ end
22
+
23
+ class ErrorWithMessageTest < Test::Unit::TestCase
24
+ def setup
25
+ @machine = StateMachine::Machine.new(Class.new)
26
+ @collection = StateMachine::NodeCollection.new(@machine)
27
+ end
28
+
29
+ def test_should_raise_exception_if_invalid_option_specified
30
+ exception = assert_raise(ArgumentError) { StateMachine::NodeCollection.new(@machine, :invalid => true) }
31
+ assert_equal 'Invalid key(s): invalid', exception.message
32
+ end
33
+
34
+ def test_should_raise_exception_on_lookup_if_invalid_index_specified
35
+ exception = assert_raise(ArgumentError) { @collection[:something, :invalid] }
36
+ assert_equal 'Invalid index: :invalid', exception.message
37
+ end
38
+
39
+ def test_should_raise_exception_on_fetch_if_invalid_index_specified
40
+ exception = assert_raise(ArgumentError) { @collection.fetch(:something, :invalid) }
41
+ assert_equal 'Invalid index: :invalid', exception.message
42
+ end
43
+ end
@@ -29,6 +29,7 @@ class EventCollectionTest < Test::Unit::TestCase
29
29
  @events = StateMachine::EventCollection.new(machine)
30
30
 
31
31
  @events << @open = StateMachine::Event.new(machine, :enable)
32
+ machine.events.concat(@events)
32
33
  end
33
34
 
34
35
  def test_should_index_by_name
@@ -50,42 +51,72 @@ class EventCollectionWithEventsWithTransitionsTest < Test::Unit::TestCase
50
51
  @machine = StateMachine::Machine.new(@klass, :initial => :parked)
51
52
  @events = StateMachine::EventCollection.new(@machine)
52
53
 
53
- @machine.state :idling, :stalled
54
- @machine.event :ignite
54
+ @machine.state :idling, :first_gear
55
55
 
56
56
  @events << @ignite = StateMachine::Event.new(@machine, :ignite)
57
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
58
 
66
- object.state = 'stalled'
67
- assert_equal [@ignite], @events.valid_for(object)
59
+ @events << @park = StateMachine::Event.new(@machine, :park)
60
+ @park.transition :idling => :parked
68
61
 
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}
62
+ @events << @shift_up = StateMachine::Event.new(@machine, :shift_up)
63
+ @shift_up.transition :parked => :first_gear
64
+ @shift_up.transition :idling => :first_gear, :if => lambda{false}
77
65
 
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}
66
+ @machine.events.concat(@events)
80
67
 
81
- object.state = 'idling'
82
- assert_equal [], @events.transitions_for(object)
68
+ @object = @klass.new
83
69
  end
84
70
 
85
- def test_should_filter_valid_transitions_for_an_object_if_requirements_specified
86
- object = @klass.new
87
- assert_equal [{:object => object, :attribute => :state, :event => :ignite, :from => 'stalled', :to => 'idling'}], @events.transitions_for(object, :from => :stalled).map {|transition| transition.attributes}
88
- assert_equal [], @events.transitions_for(object, :from => :idling).map {|transition| transition.attributes}
71
+ def test_should_find_valid_events_based_on_current_state
72
+ assert_equal [@ignite, @shift_up], @events.valid_for(@object)
73
+ end
74
+
75
+ def test_should_filter_valid_events_by_from_state
76
+ assert_equal [@park], @events.valid_for(@object, :from => :idling)
77
+ end
78
+
79
+ def test_should_filter_valid_events_by_to_state
80
+ assert_equal [@shift_up], @events.valid_for(@object, :to => :first_gear)
81
+ end
82
+
83
+ def test_should_filter_valid_events_by_event
84
+ assert_equal [@ignite], @events.valid_for(@object, :on => :ignite)
85
+ end
86
+
87
+ def test_should_filter_valid_events_by_multiple_requirements
88
+ assert_equal [], @events.valid_for(@object, :from => :idling, :to => :first_gear)
89
+ end
90
+
91
+ def test_should_allow_finding_valid_events_without_guards
92
+ assert_equal [@shift_up], @events.valid_for(@object, :from => :idling, :to => :first_gear, :guard => false)
93
+ end
94
+
95
+ def test_should_find_valid_transitions_based_on_current_state
96
+ assert_equal [
97
+ StateMachine::Transition.new(@object, @machine, :ignite, :parked, :idling),
98
+ StateMachine::Transition.new(@object, @machine, :shift_up, :parked, :first_gear)
99
+ ], @events.transitions_for(@object)
100
+ end
101
+
102
+ def test_should_filter_valid_transitions_by_from_state
103
+ assert_equal [StateMachine::Transition.new(@object, @machine, :park, :idling, :parked)], @events.transitions_for(@object, :from => :idling)
104
+ end
105
+
106
+ def test_should_filter_valid_transitions_by_to_state
107
+ assert_equal [StateMachine::Transition.new(@object, @machine, :shift_up, :parked, :first_gear)], @events.transitions_for(@object, :to => :first_gear)
108
+ end
109
+
110
+ def test_should_filter_valid_transitions_by_event
111
+ assert_equal [StateMachine::Transition.new(@object, @machine, :ignite, :parked, :idling)], @events.transitions_for(@object, :on => :ignite)
112
+ end
113
+
114
+ def test_should_filter_valid_transitions_by_multiple_requirements
115
+ assert_equal [], @events.transitions_for(@object, :from => :idling, :to => :first_gear)
116
+ end
117
+
118
+ def test_should_allow_finding_valid_transitions_without_guards
119
+ assert_equal [StateMachine::Transition.new(@object, @machine, :shift_up, :idling, :first_gear)], @events.transitions_for(@object, :from => :idling, :to => :first_gear, :guard => false)
89
120
  end
90
121
  end
91
122
 
@@ -103,6 +134,8 @@ class EventCollectionWithMultipleEventsTest < Test::Unit::TestCase
103
134
 
104
135
  @events << @shift_down = StateMachine::Event.new(@machine, :shift_down)
105
136
  @shift_down.transition :first_gear => :parked
137
+
138
+ @machine.events.concat(@events)
106
139
  end
107
140
 
108
141
  def test_should_only_include_all_valid_events_for_an_object
@@ -117,9 +150,8 @@ class EventCollectionWithoutMachineActionTest < Test::Unit::TestCase
117
150
  @klass = Class.new
118
151
  @machine = StateMachine::Machine.new(@klass, :initial => :parked)
119
152
  @events = StateMachine::EventCollection.new(@machine)
120
-
121
- @machine.event :ignite
122
153
  @events << StateMachine::Event.new(@machine, :ignite)
154
+ @machine.events.concat(@events)
123
155
 
124
156
  @object = @klass.new
125
157
  end
@@ -139,9 +171,9 @@ class EventCollectionAttributeWithMachineActionTest < Test::Unit::TestCase
139
171
  @machine = StateMachine::Machine.new(@klass, :initial => :parked, :action => :save)
140
172
  @events = StateMachine::EventCollection.new(@machine)
141
173
 
142
- @machine.event :ignite
143
174
  @machine.state :parked, :idling
144
175
  @events << @ignite = StateMachine::Event.new(@machine, :ignite)
176
+ @machine.events.concat(@events)
145
177
 
146
178
  @object = @klass.new
147
179
  end
@@ -200,9 +232,9 @@ class EventCollectionAttributeWithNamespacedMachineTest < Test::Unit::TestCase
200
232
  @machine = StateMachine::Machine.new(@klass, :namespace => 'alarm', :initial => :active, :action => :save)
201
233
  @events = StateMachine::EventCollection.new(@machine)
202
234
 
203
- @machine.event :disable
204
235
  @machine.state :active, :off
205
236
  @events << @disable = StateMachine::Event.new(@machine, :disable)
237
+ @machine.events.concat(@events)
206
238
 
207
239
  @object = @klass.new
208
240
  end
@@ -228,6 +260,8 @@ end
228
260
  class EventCollectionWithValidationsTest < Test::Unit::TestCase
229
261
  def setup
230
262
  StateMachine::Integrations.const_set('Custom', Module.new do
263
+ include StateMachine::Integrations::Base
264
+
231
265
  def invalidate(object, attribute, message, values = [])
232
266
  (object.errors ||= []) << generate_message(message, values)
233
267
  end
@@ -249,9 +283,9 @@ class EventCollectionWithValidationsTest < Test::Unit::TestCase
249
283
  @machine = StateMachine::Machine.new(@klass, :initial => :parked, :action => :save, :integration => :custom)
250
284
  @events = StateMachine::EventCollection.new(@machine)
251
285
 
252
- @machine.event :ignite
253
286
  @parked, @idling = @machine.state :parked, :idling
254
287
  @events << @ignite = StateMachine::Event.new(@machine, :ignite)
288
+ @machine.events.concat(@events)
255
289
 
256
290
  @object = @klass.new
257
291
  end
@@ -303,9 +337,9 @@ class EventCollectionWithCustomMachineAttributeTest < Test::Unit::TestCase
303
337
  @machine = StateMachine::Machine.new(@klass, :state, :attribute => :state_id, :initial => :parked, :action => :save)
304
338
  @events = StateMachine::EventCollection.new(@machine)
305
339
 
306
- @machine.event :ignite
307
340
  @machine.state :parked, :idling
308
341
  @events << @ignite = StateMachine::Event.new(@machine, :ignite)
342
+ @machine.events.concat(@events)
309
343
 
310
344
  @object = @klass.new
311
345
  end
@@ -4,7 +4,7 @@ class EventByDefaultTest < Test::Unit::TestCase
4
4
  def setup
5
5
  @klass = Class.new
6
6
  @machine = StateMachine::Machine.new(@klass)
7
- @event = StateMachine::Event.new(@machine, :ignite)
7
+ @machine.events << @event = StateMachine::Event.new(@machine, :ignite)
8
8
 
9
9
  @object = @klass.new
10
10
  end
@@ -25,8 +25,8 @@ class EventByDefaultTest < Test::Unit::TestCase
25
25
  assert_equal 'ignite', @event.human_name
26
26
  end
27
27
 
28
- def test_should_not_have_any_guards
29
- assert @event.guards.empty?
28
+ def test_should_not_have_any_branches
29
+ assert @event.branches.empty?
30
30
  end
31
31
 
32
32
  def test_should_have_no_known_states
@@ -61,7 +61,7 @@ end
61
61
  class EventTest < Test::Unit::TestCase
62
62
  def setup
63
63
  @machine = StateMachine::Machine.new(Class.new)
64
- @event = StateMachine::Event.new(@machine, :ignite)
64
+ @machine.events << @event = StateMachine::Event.new(@machine, :ignite)
65
65
  @event.transition :parked => :idling
66
66
  end
67
67
 
@@ -95,7 +95,7 @@ class EventWithHumanNameTest < Test::Unit::TestCase
95
95
  def setup
96
96
  @klass = Class.new
97
97
  @machine = StateMachine::Machine.new(@klass)
98
- @event = StateMachine::Event.new(@machine, :ignite, :human_name => 'start')
98
+ @machine.events << @event = StateMachine::Event.new(@machine, :ignite, :human_name => 'start')
99
99
  end
100
100
 
101
101
  def test_should_use_custom_human_name
@@ -107,7 +107,7 @@ class EventWithDynamicHumanNameTest < Test::Unit::TestCase
107
107
  def setup
108
108
  @klass = Class.new
109
109
  @machine = StateMachine::Machine.new(@klass)
110
- @event = StateMachine::Event.new(@machine, :ignite, :human_name => lambda {|event, object| ['start', object]})
110
+ @machine.events << @event = StateMachine::Event.new(@machine, :ignite, :human_name => lambda {|event, object| ['start', object]})
111
111
  end
112
112
 
113
113
  def test_should_use_custom_human_name
@@ -147,7 +147,7 @@ class EventWithConflictingHelpersTest < Test::Unit::TestCase
147
147
  end
148
148
  end
149
149
  @machine = StateMachine::Machine.new(@klass)
150
- @state = StateMachine::Event.new(@machine, :ignite)
150
+ @machine.events << @event = StateMachine::Event.new(@machine, :ignite)
151
151
  @object = @klass.new
152
152
  end
153
153
 
@@ -198,11 +198,58 @@ class EventWithConflictingHelpersTest < Test::Unit::TestCase
198
198
  end
199
199
  end
200
200
 
201
+ class EventWithConflictingMachineTest < Test::Unit::TestCase
202
+ def setup
203
+ require 'stringio'
204
+ @original_stderr, $stderr = $stderr, StringIO.new
205
+
206
+ @klass = Class.new
207
+ @state_machine = StateMachine::Machine.new(@klass, :state)
208
+ @state_machine.state :parked, :idling
209
+ @state_machine.events << @state_event = StateMachine::Event.new(@state_machine, :ignite)
210
+ end
211
+
212
+ def test_should_not_overwrite_first_event
213
+ @status_machine = StateMachine::Machine.new(@klass, :status)
214
+ @status_machine.state :first_gear, :second_gear
215
+ @status_machine.events << @status_event = StateMachine::Event.new(@status_machine, :ignite)
216
+
217
+ @object = @klass.new
218
+ @object.state = 'parked'
219
+ @object.status = 'first_gear'
220
+
221
+ @state_event.transition(:parked => :idling)
222
+ @status_event.transition(:parked => :first_gear)
223
+
224
+ @object.ignite
225
+ assert_equal 'idling', @object.state
226
+ assert_equal 'first_gear', @object.status
227
+ end
228
+
229
+ def test_should_output_warning
230
+ @status_machine = StateMachine::Machine.new(@klass, :status)
231
+ @status_machine.events << @status_event = StateMachine::Event.new(@status_machine, :ignite)
232
+
233
+ assert_equal "Event :ignite for :status is already defined in :state\n", $stderr.string
234
+ end
235
+
236
+ def test_should_not_output_warning_if_using_different_namespace
237
+ @status_machine = StateMachine::Machine.new(@klass, :status, :namespace => 'alarm')
238
+ @status_machine.events << @status_event = StateMachine::Event.new(@status_machine, :ignite)
239
+
240
+ assert_equal '', $stderr.string
241
+ end
242
+
243
+ def teardown
244
+ $stderr = @original_stderr
245
+ end
246
+ end
247
+
201
248
  class EventWithNamespaceTest < Test::Unit::TestCase
202
249
  def setup
203
250
  @klass = Class.new
204
251
  @machine = StateMachine::Machine.new(@klass, :namespace => 'alarm')
205
- @event = StateMachine::Event.new(@machine, :enable)
252
+ @machine.events << @event = StateMachine::Event.new(@machine, :enable)
206
253
  @object = @klass.new
207
254
  end
208
255
 
@@ -234,7 +281,7 @@ end
234
281
  class EventTransitionsTest < Test::Unit::TestCase
235
282
  def setup
236
283
  @machine = StateMachine::Machine.new(Class.new)
237
- @event = StateMachine::Event.new(@machine, :ignite)
284
+ @machine.events << @event = StateMachine::Event.new(@machine, :ignite)
238
285
  end
239
286
 
240
287
  def test_should_not_raise_exception_if_implicit_option_specified
@@ -247,9 +294,9 @@ class EventTransitionsTest < Test::Unit::TestCase
247
294
  end
248
295
 
249
296
  def test_should_automatically_set_on_option
250
- guard = @event.transition(:to => :idling)
251
- assert_instance_of StateMachine::WhitelistMatcher, guard.event_requirement
252
- assert_equal [:ignite], guard.event_requirement.values
297
+ branch = @event.transition(:to => :idling)
298
+ assert_instance_of StateMachine::WhitelistMatcher, branch.event_requirement
299
+ assert_equal [:ignite], branch.event_requirement.values
253
300
  end
254
301
 
255
302
  def test_should_not_allow_except_to_option
@@ -283,20 +330,20 @@ class EventTransitionsTest < Test::Unit::TestCase
283
330
  end
284
331
 
285
332
  def test_should_have_transitions
286
- guard = @event.transition(:to => :idling)
287
- assert_equal [guard], @event.guards
333
+ branch = @event.transition(:to => :idling)
334
+ assert_equal [branch], @event.branches
288
335
  end
289
336
  end
290
337
 
291
338
  class EventAfterBeingCopiedTest < Test::Unit::TestCase
292
339
  def setup
293
340
  @machine = StateMachine::Machine.new(Class.new)
294
- @event = StateMachine::Event.new(@machine, :ignite)
341
+ @machine.events << @event = StateMachine::Event.new(@machine, :ignite)
295
342
  @copied_event = @event.dup
296
343
  end
297
344
 
298
- def test_should_not_have_the_same_collection_of_guards
299
- assert_not_same @event.guards, @copied_event.guards
345
+ def test_should_not_have_the_same_collection_of_branches
346
+ assert_not_same @event.branches, @copied_event.branches
300
347
  end
301
348
 
302
349
  def test_should_not_have_the_same_collection_of_known_states
@@ -308,7 +355,7 @@ class EventWithoutTransitionsTest < Test::Unit::TestCase
308
355
  def setup
309
356
  @klass = Class.new
310
357
  @machine = StateMachine::Machine.new(@klass)
311
- @event = StateMachine::Event.new(@machine, :ignite)
358
+ @machine.events << @event = StateMachine::Event.new(@machine, :ignite)
312
359
  @object = @klass.new
313
360
  end
314
361
 
@@ -334,7 +381,7 @@ class EventWithTransitionsTest < Test::Unit::TestCase
334
381
  def setup
335
382
  @klass = Class.new
336
383
  @machine = StateMachine::Machine.new(@klass)
337
- @event = StateMachine::Event.new(@machine, :ignite)
384
+ @machine.events << @event = StateMachine::Event.new(@machine, :ignite)
338
385
  @event.transition(:parked => :idling)
339
386
  @event.transition(:first_gear => :idling)
340
387
  end
@@ -361,7 +408,7 @@ class EventWithoutMatchingTransitionsTest < Test::Unit::TestCase
361
408
  @machine = StateMachine::Machine.new(@klass)
362
409
  @machine.state :parked, :idling
363
410
 
364
- @event = StateMachine::Event.new(@machine, :ignite)
411
+ @machine.events << @event = StateMachine::Event.new(@machine, :ignite)
365
412
  @event.transition(:parked => :idling)
366
413
 
367
414
  @object = @klass.new
@@ -372,10 +419,18 @@ class EventWithoutMatchingTransitionsTest < Test::Unit::TestCase
372
419
  assert !@event.can_fire?(@object)
373
420
  end
374
421
 
422
+ def test_should_be_able_to_fire_with_custom_from_state
423
+ assert @event.can_fire?(@object, :from => :parked)
424
+ end
425
+
375
426
  def test_should_not_have_a_transition
376
427
  assert_nil @event.transition_for(@object)
377
428
  end
378
429
 
430
+ def test_should_have_a_transition_with_custom_from_state
431
+ assert_not_nil @event.transition_for(@object, :from => :parked)
432
+ end
433
+
379
434
  def test_should_not_fire
380
435
  assert !@event.fire(@object)
381
436
  end
@@ -389,6 +444,8 @@ end
389
444
  class EventWithMatchingDisabledTransitionsTest < Test::Unit::TestCase
390
445
  def setup
391
446
  StateMachine::Integrations.const_set('Custom', Module.new do
447
+ include StateMachine::Integrations::Base
448
+
392
449
  def invalidate(object, attribute, message, values = [])
393
450
  (object.errors ||= []) << generate_message(message, values)
394
451
  end
@@ -405,7 +462,7 @@ class EventWithMatchingDisabledTransitionsTest < Test::Unit::TestCase
405
462
  @machine = StateMachine::Machine.new(@klass, :integration => :custom)
406
463
  @machine.state :parked, :idling
407
464
 
408
- @event = StateMachine::Event.new(@machine, :ignite)
465
+ @machine.events << @event = StateMachine::Event.new(@machine, :ignite)
409
466
  @event.transition(:parked => :idling, :if => lambda {false})
410
467
 
411
468
  @object = @klass.new
@@ -416,10 +473,18 @@ class EventWithMatchingDisabledTransitionsTest < Test::Unit::TestCase
416
473
  assert !@event.can_fire?(@object)
417
474
  end
418
475
 
476
+ def test_should_be_able_to_fire_with_disabled_guards
477
+ assert @event.can_fire?(@object, :guard => false)
478
+ end
479
+
419
480
  def test_should_not_have_a_transition
420
481
  assert_nil @event.transition_for(@object)
421
482
  end
422
483
 
484
+ def test_should_have_a_transition_with_disabled_guards
485
+ assert_not_nil @event.transition_for(@object, :guard => false)
486
+ end
487
+
423
488
  def test_should_not_fire
424
489
  assert !@event.fire(@object)
425
490
  end
@@ -447,6 +512,22 @@ class EventWithMatchingDisabledTransitionsTest < Test::Unit::TestCase
447
512
  assert_equal ['cannot transition via "ignite"'], @object.errors
448
513
  end
449
514
 
515
+ def test_should_run_failure_callbacks
516
+ callback_args = nil
517
+ @machine.after_failure {|*args| callback_args = args}
518
+
519
+ @event.fire(@object)
520
+
521
+ object, transition = callback_args
522
+ assert_equal @object, object
523
+ assert_not_nil transition
524
+ assert_equal @object, transition.object
525
+ assert_equal @machine, transition.machine
526
+ assert_equal :ignite, transition.event
527
+ assert_equal :parked, transition.from_name
528
+ assert_equal :parked, transition.to_name
529
+ end
530
+
450
531
  def teardown
451
532
  StateMachine::Integrations.send(:remove_const, 'Custom')
452
533
  end
@@ -455,6 +536,8 @@ end
455
536
  class EventWithMatchingEnabledTransitionsTest < Test::Unit::TestCase
456
537
  def setup
457
538
  StateMachine::Integrations.const_set('Custom', Module.new do
539
+ include StateMachine::Integrations::Base
540
+
458
541
  def invalidate(object, attribute, message, values = [])
459
542
  (object.errors ||= []) << generate_message(message, values)
460
543
  end
@@ -470,9 +553,8 @@ class EventWithMatchingEnabledTransitionsTest < Test::Unit::TestCase
470
553
 
471
554
  @machine = StateMachine::Machine.new(@klass, :integration => :custom)
472
555
  @machine.state :parked, :idling
473
- @machine.event :ignite
474
556
 
475
- @event = StateMachine::Event.new(@machine, :ignite)
557
+ @machine.events << @event = StateMachine::Event.new(@machine, :ignite)
476
558
  @event.transition(:parked => :idling)
477
559
 
478
560
  @object = @klass.new
@@ -522,9 +604,8 @@ class EventWithTransitionWithoutToStateTest < Test::Unit::TestCase
522
604
  @klass = Class.new
523
605
  @machine = StateMachine::Machine.new(@klass)
524
606
  @machine.state :parked
525
- @machine.event :park
526
607
 
527
- @event = StateMachine::Event.new(@machine, :park)
608
+ @machine.events << @event = StateMachine::Event.new(@machine, :park)
528
609
  @event.transition(:from => :parked)
529
610
 
530
611
  @object = @klass.new
@@ -558,9 +639,8 @@ class EventWithTransitionWithNilToStateTest < Test::Unit::TestCase
558
639
  @klass = Class.new
559
640
  @machine = StateMachine::Machine.new(@klass)
560
641
  @machine.state nil, :idling
561
- @machine.event :park
562
642
 
563
- @event = StateMachine::Event.new(@machine, :park)
643
+ @machine.events << @event = StateMachine::Event.new(@machine, :park)
564
644
  @event.transition(:idling => nil)
565
645
 
566
646
  @object = @klass.new
@@ -594,9 +674,8 @@ class EventWithMultipleTransitionsTest < Test::Unit::TestCase
594
674
  @klass = Class.new
595
675
  @machine = StateMachine::Machine.new(@klass)
596
676
  @machine.state :parked, :idling
597
- @machine.event :ignite
598
677
 
599
- @event = StateMachine::Event.new(@machine, :ignite)
678
+ @machine.events << @event = StateMachine::Event.new(@machine, :ignite)
600
679
  @event.transition(:idling => :idling)
601
680
  @event.transition(:parked => :idling) # This one should get used
602
681
  @event.transition(:parked => :parked)
@@ -635,12 +714,9 @@ class EventWithMultipleTransitionsTest < Test::Unit::TestCase
635
714
  assert_equal :ignite, transition.event
636
715
  end
637
716
 
638
- def test_should_allow_specific_transition_selection_using_on
639
- transition = @event.transition_for(@object, :on => :park)
640
- assert_nil transition
641
-
642
- transition = @event.transition_for(@object, :on => :ignite)
643
- assert_not_nil transition
717
+ def test_should_not_allow_specific_transition_selection_using_on
718
+ exception = assert_raise(ArgumentError) { @event.transition_for(@object, :on => :park) }
719
+ assert_equal 'Invalid key(s): on', exception.message
644
720
  end
645
721
 
646
722
  def test_should_fire
@@ -689,9 +765,8 @@ class EventWithInvalidCurrentStateTest < Test::Unit::TestCase
689
765
  @klass = Class.new
690
766
  @machine = StateMachine::Machine.new(@klass)
691
767
  @machine.state :parked, :idling
692
- @machine.event :ignite
693
768
 
694
- @event = StateMachine::Event.new(@machine, :ignite)
769
+ @machine.events << @event = StateMachine::Event.new(@machine, :ignite)
695
770
  @event.transition(:parked => :idling)
696
771
 
697
772
  @object = @klass.new
@@ -714,6 +789,58 @@ class EventWithInvalidCurrentStateTest < Test::Unit::TestCase
714
789
  end
715
790
  end
716
791
 
792
+ class EventOnFailureTest < Test::Unit::TestCase
793
+ def setup
794
+ StateMachine::Integrations.const_set('Custom', Module.new do
795
+ include StateMachine::Integrations::Base
796
+
797
+ def invalidate(object, attribute, message, values = [])
798
+ (object.errors ||= []) << generate_message(message, values)
799
+ end
800
+
801
+ def reset(object)
802
+ object.errors = []
803
+ end
804
+ end)
805
+
806
+ @klass = Class.new do
807
+ attr_accessor :errors
808
+ end
809
+
810
+ @machine = StateMachine::Machine.new(@klass, :integration => :custom)
811
+ @machine.state :parked
812
+ @machine.events << @event = StateMachine::Event.new(@machine, :ignite)
813
+
814
+ @object = @klass.new
815
+ @object.state = 'parked'
816
+ end
817
+
818
+ def test_should_invalidate_the_state
819
+ @event.fire(@object)
820
+ assert_equal ['cannot transition via "ignite"'], @object.errors
821
+ end
822
+
823
+ def test_should_run_failure_callbacks
824
+ callback_args = nil
825
+ @machine.after_failure {|*args| callback_args = args}
826
+
827
+ @event.fire(@object)
828
+
829
+ object, transition = callback_args
830
+ assert_equal @object, object
831
+ assert_not_nil transition
832
+ assert_equal @object, transition.object
833
+ assert_equal @machine, transition.machine
834
+ assert_equal :ignite, transition.event
835
+ assert_equal :parked, transition.from_name
836
+ assert_equal :parked, transition.to_name
837
+ end
838
+
839
+ def teardown
840
+ StateMachine::Integrations.send(:remove_const, 'Custom')
841
+ end
842
+ end
843
+
717
844
  class EventWithMarshallingTest < Test::Unit::TestCase
718
845
  def setup
719
846
  @klass = Class.new do
@@ -774,7 +901,7 @@ begin
774
901
  graph = GraphViz.new('G')
775
902
  states.each {|state| graph.add_node(state.to_s)}
776
903
 
777
- @event = StateMachine::Event.new(@machine , :park)
904
+ @machine.events << @event = StateMachine::Event.new(@machine , :park)
778
905
  @event.transition :parked => :idling
779
906
  @event.transition :first_gear => :parked
780
907
  @event.transition :except_from => :parked, :to => :parked