state_machine 0.3.1 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (52) hide show
  1. data/CHANGELOG.rdoc +26 -0
  2. data/README.rdoc +254 -46
  3. data/Rakefile +29 -3
  4. data/examples/AutoShop_state.png +0 -0
  5. data/examples/Car_state.jpg +0 -0
  6. data/examples/Vehicle_state.png +0 -0
  7. data/lib/state_machine.rb +161 -116
  8. data/lib/state_machine/assertions.rb +21 -0
  9. data/lib/state_machine/callback.rb +168 -0
  10. data/lib/state_machine/eval_helpers.rb +67 -0
  11. data/lib/state_machine/event.rb +135 -101
  12. data/lib/state_machine/extensions.rb +83 -0
  13. data/lib/state_machine/guard.rb +115 -0
  14. data/lib/state_machine/integrations/active_record.rb +242 -0
  15. data/lib/state_machine/integrations/data_mapper.rb +198 -0
  16. data/lib/state_machine/integrations/data_mapper/observer.rb +153 -0
  17. data/lib/state_machine/integrations/sequel.rb +169 -0
  18. data/lib/state_machine/machine.rb +746 -352
  19. data/lib/state_machine/transition.rb +104 -212
  20. data/test/active_record.log +34865 -0
  21. data/test/classes/switch.rb +11 -0
  22. data/test/data_mapper.log +14015 -0
  23. data/test/functional/state_machine_test.rb +249 -15
  24. data/test/sequel.log +3835 -0
  25. data/test/test_helper.rb +3 -12
  26. data/test/unit/assertions_test.rb +13 -0
  27. data/test/unit/callback_test.rb +189 -0
  28. data/test/unit/eval_helpers_test.rb +92 -0
  29. data/test/unit/event_test.rb +247 -113
  30. data/test/unit/guard_test.rb +420 -0
  31. data/test/unit/integrations/active_record_test.rb +515 -0
  32. data/test/unit/integrations/data_mapper_test.rb +407 -0
  33. data/test/unit/integrations/sequel_test.rb +244 -0
  34. data/test/unit/invalid_transition_test.rb +1 -1
  35. data/test/unit/machine_test.rb +1056 -98
  36. data/test/unit/state_machine_test.rb +14 -113
  37. data/test/unit/transition_test.rb +269 -495
  38. metadata +44 -30
  39. data/test/app_root/app/models/auto_shop.rb +0 -34
  40. data/test/app_root/app/models/car.rb +0 -19
  41. data/test/app_root/app/models/highway.rb +0 -3
  42. data/test/app_root/app/models/motorcycle.rb +0 -3
  43. data/test/app_root/app/models/switch.rb +0 -23
  44. data/test/app_root/app/models/switch_observer.rb +0 -20
  45. data/test/app_root/app/models/toggle_switch.rb +0 -2
  46. data/test/app_root/app/models/vehicle.rb +0 -78
  47. data/test/app_root/config/environment.rb +0 -7
  48. data/test/app_root/db/migrate/001_create_switches.rb +0 -12
  49. data/test/app_root/db/migrate/002_create_auto_shops.rb +0 -13
  50. data/test/app_root/db/migrate/003_create_highways.rb +0 -11
  51. data/test/app_root/db/migrate/004_create_vehicles.rb +0 -16
  52. data/test/factory.rb +0 -77
@@ -0,0 +1,420 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../test_helper')
2
+
3
+ class GuardTest < Test::Unit::TestCase
4
+ def setup
5
+ @guard = StateMachine::Guard.new(:to => 'on', :from => 'off')
6
+ end
7
+
8
+ def test_should_raise_exception_if_invalid_option_specified
9
+ assert_raise(ArgumentError) { StateMachine::Guard.new(:invalid => true) }
10
+ end
11
+
12
+ def test_should_have_requirements
13
+ expected = {:to => 'on', :from => 'off'}
14
+ assert_equal expected, @guard.requirements
15
+ end
16
+ end
17
+
18
+ class GuardWithNoRequirementsTest < Test::Unit::TestCase
19
+ def setup
20
+ @object = Object.new
21
+ @guard = StateMachine::Guard.new
22
+ end
23
+
24
+ def test_should_match_nil_query
25
+ assert @guard.matches?(@object, nil)
26
+ end
27
+
28
+ def test_should_match_empty_query
29
+ assert @guard.matches?(@object, {})
30
+ end
31
+
32
+ def test_should_match_non_empty_query
33
+ assert @guard.matches?(@object, :from => 'off', :to => 'on', :on => 'turn_on')
34
+ end
35
+ end
36
+
37
+ class GuardWithToRequirementTest < Test::Unit::TestCase
38
+ def setup
39
+ @object = Object.new
40
+ @guard = StateMachine::Guard.new(:to => 'on')
41
+ end
42
+
43
+ def test_should_match_if_included
44
+ assert @guard.matches?(@object, :to => 'on')
45
+ end
46
+
47
+ def test_should_not_match_if_not_included
48
+ assert !@guard.matches?(@object, :to => 'off')
49
+ end
50
+
51
+ def test_should_ignore_from
52
+ assert @guard.matches?(@object, :to => 'on', :from => 'off')
53
+ end
54
+
55
+ def test_should_ignore_on
56
+ assert @guard.matches?(@object, :to => 'on', :on => 'turn_on')
57
+ end
58
+
59
+ def test_should_be_included_in_known_states
60
+ assert_equal %w(on), @guard.known_states
61
+ end
62
+ end
63
+
64
+ class GuardWithMultipleToRequirementsTest < Test::Unit::TestCase
65
+ def setup
66
+ @object = Object.new
67
+ @guard = StateMachine::Guard.new(:to => %w(on off))
68
+ end
69
+
70
+ def test_should_match_if_included
71
+ assert @guard.matches?(@object, :to => 'on')
72
+ end
73
+
74
+ def test_should_not_match_if_not_included
75
+ assert !@guard.matches?(@object, :to => 'maybe')
76
+ end
77
+
78
+ def test_should_be_included_in_known_states
79
+ assert_equal %w(on off), @guard.known_states
80
+ end
81
+ end
82
+
83
+ class GuardWithFromRequirementTest < Test::Unit::TestCase
84
+ def setup
85
+ @object = Object.new
86
+ @guard = StateMachine::Guard.new(:from => 'on')
87
+ end
88
+
89
+ def test_should_match_if_included
90
+ assert @guard.matches?(@object, :from => 'on')
91
+ end
92
+
93
+ def test_should_not_match_if_not_included
94
+ assert !@guard.matches?(@object, :from => 'off')
95
+ end
96
+
97
+ def test_should_ignore_to
98
+ assert @guard.matches?(@object, :from => 'on', :to => 'off')
99
+ end
100
+
101
+ def test_should_ignore_on
102
+ assert @guard.matches?(@object, :from => 'on', :on => 'turn_on')
103
+ end
104
+
105
+ def test_should_be_included_in_known_states
106
+ assert_equal %w(on), @guard.known_states
107
+ end
108
+ end
109
+
110
+ class GuardWithMultipleFromRequirementsTest < Test::Unit::TestCase
111
+ def setup
112
+ @object = Object.new
113
+ @guard = StateMachine::Guard.new(:from => %w(on off))
114
+ end
115
+
116
+ def test_should_match_if_included
117
+ assert @guard.matches?(@object, :from => 'on')
118
+ end
119
+
120
+ def test_should_not_match_if_not_included
121
+ assert !@guard.matches?(@object, :from => 'maybe')
122
+ end
123
+
124
+ def test_should_be_included_in_known_states
125
+ assert_equal %w(on off), @guard.known_states
126
+ end
127
+ end
128
+
129
+ class GuardWithOnRequirementTest < Test::Unit::TestCase
130
+ def setup
131
+ @object = Object.new
132
+ @guard = StateMachine::Guard.new(:on => 'turn_on')
133
+ end
134
+
135
+ def test_should_match_if_included
136
+ assert @guard.matches?(@object, :on => 'turn_on')
137
+ end
138
+
139
+ def test_should_not_match_if_not_included
140
+ assert !@guard.matches?(@object, :on => 'turn_off')
141
+ end
142
+
143
+ def test_should_ignore_to
144
+ assert @guard.matches?(@object, :on => 'turn_on', :to => 'off')
145
+ end
146
+
147
+ def test_should_ignore_from
148
+ assert @guard.matches?(@object, :on => 'turn_on', :from => 'off')
149
+ end
150
+
151
+ def test_should_not_be_included_in_known_states
152
+ assert_equal [], @guard.known_states
153
+ end
154
+ end
155
+
156
+ class GuardWithMultipleOnRequirementsTest < Test::Unit::TestCase
157
+ def setup
158
+ @object = Object.new
159
+ @guard = StateMachine::Guard.new(:on => %w(turn_on turn_off))
160
+ end
161
+
162
+ def test_should_match_if_included
163
+ assert @guard.matches?(@object, :on => 'turn_on')
164
+ end
165
+
166
+ def test_should_not_match_if_not_included
167
+ assert !@guard.matches?(@object, :on => 'turn_down')
168
+ end
169
+ end
170
+
171
+ class GuardWithExceptToRequirementTest < Test::Unit::TestCase
172
+ def setup
173
+ @object = Object.new
174
+ @guard = StateMachine::Guard.new(:except_to => 'off')
175
+ end
176
+
177
+ def test_should_match_if_not_included
178
+ assert @guard.matches?(@object, :to => 'on')
179
+ end
180
+
181
+ def test_should_not_match_if_included
182
+ assert !@guard.matches?(@object, :to => 'off')
183
+ end
184
+
185
+ def test_should_ignore_from
186
+ assert @guard.matches?(@object, :except_to => 'off', :from => 'off')
187
+ end
188
+
189
+ def test_should_ignore_on
190
+ assert @guard.matches?(@object, :except_to => 'off', :on => 'turn_on')
191
+ end
192
+
193
+ def test_should_be_included_in_known_states
194
+ assert_equal %w(off), @guard.known_states
195
+ end
196
+ end
197
+
198
+ class GuardWithMultipleExceptToRequirementsTest < Test::Unit::TestCase
199
+ def setup
200
+ @object = Object.new
201
+ @guard = StateMachine::Guard.new(:except_to => %w(on off))
202
+ end
203
+
204
+ def test_should_match_if_not_included
205
+ assert @guard.matches?(@object, :to => 'maybe')
206
+ end
207
+
208
+ def test_should_not_match_if_included
209
+ assert !@guard.matches?(@object, :to => 'on')
210
+ end
211
+
212
+ def test_should_be_included_in_known_states
213
+ assert_equal %w(on off), @guard.known_states
214
+ end
215
+ end
216
+
217
+ class GuardWithExceptFromRequirementTest < Test::Unit::TestCase
218
+ def setup
219
+ @object = Object.new
220
+ @guard = StateMachine::Guard.new(:except_from => 'off')
221
+ end
222
+
223
+ def test_should_match_if_not_included
224
+ assert @guard.matches?(@object, :from => 'on')
225
+ end
226
+
227
+ def test_should_not_match_if_included
228
+ assert !@guard.matches?(@object, :from => 'off')
229
+ end
230
+
231
+ def test_should_ignore_to
232
+ assert @guard.matches?(@object, :from => 'on', :to => 'off')
233
+ end
234
+
235
+ def test_should_ignore_on
236
+ assert @guard.matches?(@object, :from => 'on', :on => 'turn_on')
237
+ end
238
+
239
+ def test_should_be_included_in_known_states
240
+ assert_equal %w(off), @guard.known_states
241
+ end
242
+ end
243
+
244
+ class GuardWithMultipleExceptFromRequirementsTest < Test::Unit::TestCase
245
+ def setup
246
+ @object = Object.new
247
+ @guard = StateMachine::Guard.new(:except_from => %w(on off))
248
+ end
249
+
250
+ def test_should_match_if_not_included
251
+ assert @guard.matches?(@object, :from => 'maybe')
252
+ end
253
+
254
+ def test_should_not_match_if_included
255
+ assert !@guard.matches?(@object, :from => 'on')
256
+ end
257
+
258
+ def test_should_be_included_in_known_states
259
+ assert_equal %w(on off), @guard.known_states
260
+ end
261
+ end
262
+
263
+ class GuardWithExceptOnRequirementTest < Test::Unit::TestCase
264
+ def setup
265
+ @object = Object.new
266
+ @guard = StateMachine::Guard.new(:except_on => 'turn_off')
267
+ end
268
+
269
+ def test_should_match_if_not_included
270
+ assert @guard.matches?(@object, :on => 'turn_on')
271
+ end
272
+
273
+ def test_should_not_match_if_included
274
+ assert !@guard.matches?(@object, :on => 'turn_off')
275
+ end
276
+
277
+ def test_should_ignore_to
278
+ assert @guard.matches?(@object, :on => 'turn_on', :to => 'off')
279
+ end
280
+
281
+ def test_should_ignore_from
282
+ assert @guard.matches?(@object, :on => 'turn_on', :from => 'off')
283
+ end
284
+
285
+ def test_should_not_be_included_in_known_states
286
+ assert_equal [], @guard.known_states
287
+ end
288
+ end
289
+
290
+ class GuardWithMultipleExceptOnRequirementsTest < Test::Unit::TestCase
291
+ def setup
292
+ @object = Object.new
293
+ @guard = StateMachine::Guard.new(:except_on => %w(turn_on turn_off))
294
+ end
295
+
296
+ def test_should_match_if_not_included
297
+ assert @guard.matches?(@object, :on => 'turn_down')
298
+ end
299
+
300
+ def test_should_not_match_if_included
301
+ assert !@guard.matches?(@object, :on => 'turn_on')
302
+ end
303
+ end
304
+
305
+ class GuardWithConflictingToRequirementsTest < Test::Unit::TestCase
306
+ def setup
307
+ @object = Object.new
308
+ @guard = StateMachine::Guard.new(:to => 'on', :except_to => 'on')
309
+ end
310
+
311
+ def test_should_ignore_except_requirement
312
+ assert @guard.matches?(@object, :to => 'on')
313
+ end
314
+ end
315
+
316
+ class GuardWithConflictingFromRequirementsTest < Test::Unit::TestCase
317
+ def setup
318
+ @object = Object.new
319
+ @guard = StateMachine::Guard.new(:from => 'on', :except_from => 'on')
320
+ end
321
+
322
+ def test_should_ignore_except_requirement
323
+ assert @guard.matches?(@object, :from => 'on')
324
+ end
325
+ end
326
+
327
+ class GuardWithConflictingOnRequirementsTest < Test::Unit::TestCase
328
+ def setup
329
+ @object = Object.new
330
+ @guard = StateMachine::Guard.new(:on => 'turn_on', :except_on => 'turn_on')
331
+ end
332
+
333
+ def test_should_ignore_except_requirement
334
+ assert @guard.matches?(@object, :on => 'turn_on')
335
+ end
336
+ end
337
+
338
+ class GuardWithDifferentRequirementsTest < Test::Unit::TestCase
339
+ def setup
340
+ @object = Object.new
341
+ @guard = StateMachine::Guard.new(:from => 'off', :to => 'on', :on => 'turn_on')
342
+ end
343
+
344
+ def test_should_match_empty_query
345
+ assert @guard.matches?(@object)
346
+ end
347
+
348
+ def test_should_match_if_all_requirements_match
349
+ assert @guard.matches?(@object, :from => 'off', :to => 'on', :on => 'turn_on')
350
+ end
351
+
352
+ def test_should_not_match_if_from_not_included
353
+ assert !@guard.matches?(@object, :from => 'on')
354
+ end
355
+
356
+ def test_should_not_match_if_to_not_included
357
+ assert !@guard.matches?(@object, :to => 'off')
358
+ end
359
+
360
+ def test_should_not_match_if_on_not_included
361
+ assert !@guard.matches?(@object, :on => 'turn_off')
362
+ end
363
+
364
+ def test_should_include_all_known_states
365
+ assert_equal %w(off on), @guard.known_states.sort
366
+ end
367
+
368
+ def test_should_not_duplicate_known_statse
369
+ guard = StateMachine::Guard.new(:except_from => 'on', :to => 'on', :on => 'turn_on')
370
+ assert_equal %w(on), guard.known_states
371
+ end
372
+ end
373
+
374
+ class GuardWithIfConditionalTest < Test::Unit::TestCase
375
+ def setup
376
+ @object = Object.new
377
+ end
378
+
379
+ def test_should_match_if_true
380
+ guard = StateMachine::Guard.new(:if => lambda {true})
381
+ assert guard.matches?(@object)
382
+ end
383
+
384
+ def test_should_not_match_if_false
385
+ guard = StateMachine::Guard.new(:if => lambda {false})
386
+ assert !guard.matches?(@object)
387
+ end
388
+ end
389
+
390
+ class GuardWithUnlessConditionalTest < Test::Unit::TestCase
391
+ def setup
392
+ @object = Object.new
393
+ end
394
+
395
+ def test_should_match_if_false
396
+ guard = StateMachine::Guard.new(:unless => lambda {false})
397
+ assert guard.matches?(@object)
398
+ end
399
+
400
+ def test_should_not_match_if_true
401
+ guard = StateMachine::Guard.new(:unless => lambda {true})
402
+ assert !guard.matches?(@object)
403
+ end
404
+ end
405
+
406
+ class GuardWithConflictingConditionalsTest < Test::Unit::TestCase
407
+ def setup
408
+ @object = Object.new
409
+ end
410
+
411
+ def test_should_match_if_true
412
+ guard = StateMachine::Guard.new(:if => lambda {true}, :unless => lambda {true})
413
+ assert guard.matches?(@object)
414
+ end
415
+
416
+ def test_should_not_match_if_false
417
+ guard = StateMachine::Guard.new(:if => lambda {false}, :unless => lambda {false})
418
+ assert !guard.matches?(@object)
419
+ end
420
+ end
@@ -0,0 +1,515 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../../test_helper')
2
+
3
+ begin
4
+ # Load library
5
+ require 'rubygems'
6
+ require 'active_record'
7
+
8
+ FIXTURES_ROOT = File.dirname(__FILE__) + '/../../fixtures/'
9
+
10
+ # Load TestCase helpers
11
+ require 'active_support/test_case'
12
+ require 'active_record/fixtures'
13
+ require 'active_record/test_case'
14
+
15
+ # Set default fixtures configuration
16
+ ActiveSupport::TestCase.class_eval do
17
+ self.fixture_path = File.dirname(__FILE__) + '/../../fixtures/'
18
+ self.use_instantiated_fixtures = false
19
+ self.use_transactional_fixtures = true
20
+ end
21
+
22
+ # Establish database connection
23
+ ActiveRecord::Base.establish_connection({'adapter' => 'sqlite3', 'database' => ':memory:'})
24
+ ActiveRecord::Base.logger = Logger.new("#{File.dirname(__FILE__)}/../../active_record.log")
25
+
26
+ # Add model/observer creation helpers
27
+ ActiveRecord::TestCase.class_eval do
28
+ # Creates a new ActiveRecord model (and the associated table)
29
+ def new_model(create_table = true, &block)
30
+ model = Class.new(ActiveRecord::Base) do
31
+ connection.create_table(:foo, :force => true) {|t| t.string(:state)} if create_table
32
+ set_table_name('foo')
33
+
34
+ def self.name; 'ActiveRecordTest::Foo'; end
35
+ end
36
+ model.class_eval(&block) if block_given?
37
+ model
38
+ end
39
+
40
+ # Creates a new ActiveRecord observer
41
+ def new_observer(model, &block)
42
+ observer = Class.new(ActiveRecord::Observer) do
43
+ attr_accessor :notifications
44
+
45
+ def initialize
46
+ super
47
+ @notifications = []
48
+ end
49
+ end
50
+ observer.observe(model)
51
+ observer.class_eval(&block) if block_given?
52
+ observer
53
+ end
54
+ end
55
+
56
+ module ActiveRecordTest
57
+ class IntegrationTest < ActiveRecord::TestCase
58
+ def test_should_match_if_class_inherits_from_active_record
59
+ assert StateMachine::Integrations::ActiveRecord.matches?(new_model)
60
+ end
61
+
62
+ def test_should_not_match_if_class_does_not_inherit_from_active_record
63
+ assert !StateMachine::Integrations::ActiveRecord.matches?(Class.new)
64
+ end
65
+ end
66
+
67
+ class MachineByDefaultTest < ActiveRecord::TestCase
68
+ def setup
69
+ @model = new_model
70
+ @machine = StateMachine::Machine.new(@model)
71
+ end
72
+
73
+ def test_should_use_save_as_action
74
+ assert_equal :save, @machine.action
75
+ end
76
+
77
+ def test_should_create_notifier_before_callback
78
+ assert_equal 1, @machine.callbacks[:before].size
79
+ end
80
+
81
+ def test_should_create_notifier_after_callback
82
+ assert_equal 1, @machine.callbacks[:after].size
83
+ end
84
+ end
85
+
86
+ class MachineTest < ActiveRecord::TestCase
87
+ def setup
88
+ @model = new_model
89
+ @machine = StateMachine::Machine.new(@model)
90
+ end
91
+
92
+ def test_should_create_singular_with_scope
93
+ assert @model.respond_to?(:with_state)
94
+ end
95
+
96
+ def test_should_only_include_records_with_state_in_singular_with_scope
97
+ off = @model.create :state => 'off'
98
+ on = @model.create :state => 'on'
99
+
100
+ assert_equal [off], @model.with_state('off')
101
+ end
102
+
103
+ def test_should_create_plural_with_scope
104
+ assert @model.respond_to?(:with_states)
105
+ end
106
+
107
+ def test_should_only_include_records_with_states_in_plural_with_scope
108
+ off = @model.create :state => 'off'
109
+ on = @model.create :state => 'on'
110
+
111
+ assert_equal [off, on], @model.with_states('off', 'on')
112
+ end
113
+
114
+ def test_should_create_singular_without_scope
115
+ assert @model.respond_to?(:without_state)
116
+ end
117
+
118
+ def test_should_only_include_records_without_state_in_singular_without_scope
119
+ off = @model.create :state => 'off'
120
+ on = @model.create :state => 'on'
121
+
122
+ assert_equal [off], @model.without_state('on')
123
+ end
124
+
125
+ def test_should_create_plural_without_scope
126
+ assert @model.respond_to?(:without_states)
127
+ end
128
+
129
+ def test_should_only_include_records_without_states_in_plural_without_scope
130
+ off = @model.create :state => 'off'
131
+ on = @model.create :state => 'on'
132
+ error = @model.create :state => 'error'
133
+
134
+ assert_equal [off, on], @model.without_states('error')
135
+ end
136
+
137
+ def test_should_rollback_transaction_if_false
138
+ @machine.within_transaction(@model.new) do
139
+ @model.create
140
+ false
141
+ end
142
+
143
+ assert_equal 0, @model.count
144
+ end
145
+
146
+ def test_should_not_rollback_transaction_if_true
147
+ @machine.within_transaction(@model.new) do
148
+ @model.create
149
+ true
150
+ end
151
+
152
+ assert_equal 1, @model.count
153
+ end
154
+
155
+ def test_should_not_override_the_column_reader
156
+ record = @model.new
157
+ record[:state] = 'off'
158
+ assert_equal 'off', record.state
159
+ end
160
+
161
+ def test_should_not_override_the_column_writer
162
+ record = @model.new
163
+ record.state = 'off'
164
+ assert_equal 'off', record[:state]
165
+ end
166
+ end
167
+
168
+ class MachineUnmigratedTest < ActiveRecord::TestCase
169
+ def setup
170
+ @model = new_model(false)
171
+
172
+ # Drop the table so that it definitely doesn't exist
173
+ @model.connection.drop_table(:foo) if @model.connection.table_exists?(:foo)
174
+ end
175
+
176
+ def test_should_allow_machine_creation
177
+ assert_nothing_raised { StateMachine::Machine.new(@model) }
178
+ end
179
+ end
180
+
181
+ class MachineWithInitialStateTest < ActiveRecord::TestCase
182
+ def setup
183
+ @model = new_model
184
+ @machine = StateMachine::Machine.new(@model, :initial => 'off')
185
+ @record = @model.new
186
+ end
187
+
188
+ def test_should_set_initial_state_on_created_object
189
+ assert_equal 'off', @record.state
190
+ end
191
+ end
192
+
193
+ class MachineWithNonColumnStateAttributeTest < ActiveRecord::TestCase
194
+ def setup
195
+ @model = new_model
196
+ @machine = StateMachine::Machine.new(@model, :status, :initial => 'off')
197
+ @record = @model.new
198
+ end
199
+
200
+ def test_should_define_a_reader_attribute_for_the_attribute
201
+ assert @record.respond_to?(:status)
202
+ end
203
+
204
+ def test_should_define_a_writer_attribute_for_the_attribute
205
+ assert @record.respond_to?(:status=)
206
+ end
207
+
208
+ def test_should_set_initial_state_on_created_object
209
+ assert_equal 'off', @record.status
210
+ end
211
+ end
212
+
213
+ class MachineWithCallbacksTest < ActiveRecord::TestCase
214
+ def setup
215
+ @model = new_model
216
+ @machine = StateMachine::Machine.new(@model)
217
+ @record = @model.new(:state => 'off')
218
+ @transition = StateMachine::Transition.new(@record, @machine, 'turn_on', 'off', 'on')
219
+ end
220
+
221
+ def test_should_run_before_callbacks
222
+ called = false
223
+ @machine.before_transition(lambda {called = true})
224
+
225
+ @transition.perform
226
+ assert called
227
+ end
228
+
229
+ def test_should_pass_record_into_before_callbacks_with_one_argument
230
+ record = nil
231
+ @machine.before_transition(lambda {|arg| record = arg})
232
+
233
+ @transition.perform
234
+ assert_equal @record, record
235
+ end
236
+
237
+ def test_should_pass_record_and_transition_into_before_callbacks_with_multiple_arguments
238
+ callback_args = nil
239
+ @machine.before_transition(lambda {|*args| callback_args = args})
240
+
241
+ @transition.perform
242
+ assert_equal [@record, @transition], callback_args
243
+ end
244
+
245
+ def test_should_run_before_callbacks_outside_the_context_of_the_record
246
+ context = nil
247
+ @machine.before_transition(lambda {context = self})
248
+
249
+ @transition.perform
250
+ assert_equal self, context
251
+ end
252
+
253
+ def test_should_run_after_callbacks
254
+ called = false
255
+ @machine.after_transition(lambda {called = true})
256
+
257
+ @transition.perform
258
+ assert called
259
+ end
260
+
261
+ def test_should_pass_record_into_after_callbacks_with_one_argument
262
+ record = nil
263
+ @machine.after_transition(lambda {|arg| record = arg})
264
+
265
+ @transition.perform
266
+ assert_equal @record, record
267
+ end
268
+
269
+ def test_should_pass_record_transition_and_result_into_after_callbacks_with_multiple_arguments
270
+ callback_args = nil
271
+ @machine.after_transition(lambda {|*args| callback_args = args})
272
+
273
+ @transition.perform
274
+ assert_equal [@record, @transition, true], callback_args
275
+ end
276
+
277
+ def test_should_run_after_callbacks_outside_the_context_of_the_record
278
+ context = nil
279
+ @machine.after_transition(lambda {context = self})
280
+
281
+ @transition.perform
282
+ assert_equal self, context
283
+ end
284
+
285
+ def test_should_include_transition_states_in_known_states
286
+ @machine.before_transition :to => 'error', :do => lambda {}
287
+
288
+ assert_equal %w(error), @machine.states.sort
289
+ end
290
+ end
291
+
292
+ class MachineWithFailedBeforeCallbacksTest < ActiveRecord::TestCase
293
+ def setup
294
+ @before_count = 0
295
+
296
+ @model = new_model
297
+ @machine = StateMachine::Machine.new(@model)
298
+ @machine.before_transition(lambda {@before_count += 1; false})
299
+ @machine.before_transition(lambda {@before_count += 1})
300
+ @record = @model.new(:state => 'off')
301
+ @transition = StateMachine::Transition.new(@record, @machine, 'turn_on', 'off', 'on')
302
+ @result = @transition.perform
303
+ end
304
+
305
+ def test_should_not_be_successful
306
+ assert !@result
307
+ end
308
+
309
+ def test_should_not_change_current_state
310
+ assert_equal 'off', @record.state
311
+ end
312
+
313
+ def test_should_not_run_action
314
+ assert @record.new_record?
315
+ end
316
+
317
+ def test_should_not_run_further_before_callbacks
318
+ assert_equal 1, @before_count
319
+ end
320
+ end
321
+
322
+ class MachineWithFailedActionTest < ActiveRecord::TestCase
323
+ def setup
324
+ @model = new_model do
325
+ validates_inclusion_of :state, :in => %w(maybe)
326
+ end
327
+
328
+ @machine = StateMachine::Machine.new(@model)
329
+ @record = @model.new(:state => 'off')
330
+ @transition = StateMachine::Transition.new(@record, @machine, 'turn_on', 'off', 'on')
331
+ @result = @transition.perform
332
+ end
333
+
334
+ def test_should_not_be_successful
335
+ assert !@result
336
+ end
337
+
338
+ def test_should_change_current_state
339
+ assert_equal 'on', @record.state
340
+ end
341
+
342
+ def test_should_not_save_record
343
+ assert @record.new_record?
344
+ end
345
+ end
346
+
347
+ class MachineWithFailedAfterCallbacksTest < ActiveRecord::TestCase
348
+ def setup
349
+ @after_count = 0
350
+
351
+ @model = new_model
352
+ @machine = StateMachine::Machine.new(@model)
353
+ @machine.after_transition(lambda {@after_count += 1; false})
354
+ @machine.after_transition(lambda {@after_count += 1})
355
+ @record = @model.new(:state => 'off')
356
+ @transition = StateMachine::Transition.new(@record, @machine, 'turn_on', 'off', 'on')
357
+ @result = @transition.perform
358
+ end
359
+
360
+ def test_should_be_successful
361
+ assert @result
362
+ end
363
+
364
+ def test_should_change_current_state
365
+ assert_equal 'on', @record.state
366
+ end
367
+
368
+ def test_should_save_record
369
+ assert !@record.new_record?
370
+ end
371
+
372
+ def test_should_not_run_further_after_callbacks
373
+ assert_equal 1, @after_count
374
+ end
375
+ end
376
+
377
+ class MachineWithObserversTest < ActiveRecord::TestCase
378
+ def setup
379
+ @model = new_model
380
+ @machine = StateMachine::Machine.new(@model)
381
+ @record = @model.new(:state => 'off')
382
+ @transition = StateMachine::Transition.new(@record, @machine, 'turn_on', 'off', 'on')
383
+ end
384
+
385
+ def test_should_call_before_event_method
386
+ observer = new_observer(@model) do
387
+ def before_turn_on(*args)
388
+ notifications << args
389
+ end
390
+ end
391
+ instance = observer.instance
392
+
393
+ @transition.perform
394
+ assert_equal [[@record, @transition]], instance.notifications
395
+ end
396
+
397
+ def test_should_call_before_transition_method
398
+ observer = new_observer(@model) do
399
+ def before_transition(*args)
400
+ notifications << args
401
+ end
402
+ end
403
+ instance = observer.instance
404
+
405
+ @transition.perform
406
+ assert_equal [[@record, @transition]], instance.notifications
407
+ end
408
+
409
+ def test_should_call_after_event_method
410
+ observer = new_observer(@model) do
411
+ def after_turn_on(*args)
412
+ notifications << args
413
+ end
414
+ end
415
+ instance = observer.instance
416
+
417
+ @transition.perform
418
+ assert_equal [[@record, @transition]], instance.notifications
419
+ end
420
+
421
+ def test_should_call_after_transition_method
422
+ observer = new_observer(@model) do
423
+ def after_transition(*args)
424
+ notifications << args
425
+ end
426
+ end
427
+ instance = observer.instance
428
+
429
+ @transition.perform
430
+ assert_equal [[@record, @transition]], instance.notifications
431
+ end
432
+
433
+ def test_should_call_event_method_before_transition_method
434
+ observer = new_observer(@model) do
435
+ def before_turn_on(*args)
436
+ notifications << :before_turn_on
437
+ end
438
+
439
+ def before_transition(*args)
440
+ notifications << :before_transition
441
+ end
442
+ end
443
+ instance = observer.instance
444
+
445
+ @transition.perform
446
+ assert_equal [:before_turn_on, :before_transition], instance.notifications
447
+ end
448
+
449
+ def test_should_call_methods_outside_the_context_of_the_record
450
+ observer = new_observer(@model) do
451
+ def before_turn_on(*args)
452
+ notifications << self
453
+ end
454
+ end
455
+ instance = observer.instance
456
+
457
+ @transition.perform
458
+ assert_equal [instance], instance.notifications
459
+ end
460
+ end
461
+
462
+ class MachineWithMixedCallbacksTest < ActiveRecord::TestCase
463
+ def setup
464
+ @model = new_model
465
+ @machine = StateMachine::Machine.new(@model)
466
+ @record = @model.new(:state => 'off')
467
+ @transition = StateMachine::Transition.new(@record, @machine, 'turn_on', 'off', 'on')
468
+
469
+ @notifications = []
470
+
471
+ # Create callbacks
472
+ @machine.before_transition(lambda {@notifications << :callback_before_transition})
473
+ @machine.after_transition(lambda {@notifications << :callback_after_transition})
474
+
475
+ # Create observer callbacks
476
+ observer = new_observer(@model) do
477
+ def before_turn_on(*args)
478
+ notifications << :observer_before_turn_on
479
+ end
480
+
481
+ def before_transition(*args)
482
+ notifications << :observer_before_transition
483
+ end
484
+
485
+ def after_turn_on(*args)
486
+ notifications << :observer_after_turn_on
487
+ end
488
+
489
+ def after_transition(*args)
490
+ notifications << :observer_after_transition
491
+ end
492
+ end
493
+ instance = observer.instance
494
+ instance.notifications = @notifications
495
+
496
+ @transition.perform
497
+ end
498
+
499
+ def test_should_invoke_callbacks_in_specific_order
500
+ expected = [
501
+ :callback_before_transition,
502
+ :observer_before_turn_on,
503
+ :observer_before_transition,
504
+ :callback_after_transition,
505
+ :observer_after_turn_on,
506
+ :observer_after_transition
507
+ ]
508
+
509
+ assert_equal expected, @notifications
510
+ end
511
+ end
512
+ end
513
+ rescue LoadError
514
+ $stderr.puts 'Skipping ActiveRecord tests. `gem install active_record` and try again.'
515
+ end