state_machine 0.3.1 → 0.4.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 (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