verborghs-state_machine 0.9.4

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 (89) hide show
  1. data/CHANGELOG.rdoc +360 -0
  2. data/LICENSE +20 -0
  3. data/README.rdoc +635 -0
  4. data/Rakefile +77 -0
  5. data/examples/AutoShop_state.png +0 -0
  6. data/examples/Car_state.png +0 -0
  7. data/examples/TrafficLight_state.png +0 -0
  8. data/examples/Vehicle_state.png +0 -0
  9. data/examples/auto_shop.rb +11 -0
  10. data/examples/car.rb +19 -0
  11. data/examples/merb-rest/controller.rb +51 -0
  12. data/examples/merb-rest/model.rb +28 -0
  13. data/examples/merb-rest/view_edit.html.erb +24 -0
  14. data/examples/merb-rest/view_index.html.erb +23 -0
  15. data/examples/merb-rest/view_new.html.erb +13 -0
  16. data/examples/merb-rest/view_show.html.erb +17 -0
  17. data/examples/rails-rest/controller.rb +43 -0
  18. data/examples/rails-rest/migration.rb +11 -0
  19. data/examples/rails-rest/model.rb +23 -0
  20. data/examples/rails-rest/view_edit.html.erb +25 -0
  21. data/examples/rails-rest/view_index.html.erb +23 -0
  22. data/examples/rails-rest/view_new.html.erb +14 -0
  23. data/examples/rails-rest/view_show.html.erb +17 -0
  24. data/examples/traffic_light.rb +7 -0
  25. data/examples/vehicle.rb +31 -0
  26. data/init.rb +1 -0
  27. data/lib/state_machine/assertions.rb +36 -0
  28. data/lib/state_machine/callback.rb +241 -0
  29. data/lib/state_machine/condition_proxy.rb +106 -0
  30. data/lib/state_machine/eval_helpers.rb +83 -0
  31. data/lib/state_machine/event.rb +267 -0
  32. data/lib/state_machine/event_collection.rb +122 -0
  33. data/lib/state_machine/extensions.rb +149 -0
  34. data/lib/state_machine/guard.rb +230 -0
  35. data/lib/state_machine/initializers/merb.rb +1 -0
  36. data/lib/state_machine/initializers/rails.rb +5 -0
  37. data/lib/state_machine/initializers.rb +4 -0
  38. data/lib/state_machine/integrations/active_model/locale.rb +11 -0
  39. data/lib/state_machine/integrations/active_model/observer.rb +45 -0
  40. data/lib/state_machine/integrations/active_model.rb +445 -0
  41. data/lib/state_machine/integrations/active_record/locale.rb +20 -0
  42. data/lib/state_machine/integrations/active_record.rb +522 -0
  43. data/lib/state_machine/integrations/data_mapper/observer.rb +175 -0
  44. data/lib/state_machine/integrations/data_mapper.rb +379 -0
  45. data/lib/state_machine/integrations/mongo_mapper.rb +309 -0
  46. data/lib/state_machine/integrations/sequel.rb +356 -0
  47. data/lib/state_machine/integrations.rb +83 -0
  48. data/lib/state_machine/machine.rb +1645 -0
  49. data/lib/state_machine/machine_collection.rb +64 -0
  50. data/lib/state_machine/matcher.rb +123 -0
  51. data/lib/state_machine/matcher_helpers.rb +54 -0
  52. data/lib/state_machine/node_collection.rb +152 -0
  53. data/lib/state_machine/state.rb +260 -0
  54. data/lib/state_machine/state_collection.rb +112 -0
  55. data/lib/state_machine/transition.rb +399 -0
  56. data/lib/state_machine/transition_collection.rb +244 -0
  57. data/lib/state_machine.rb +421 -0
  58. data/lib/tasks/state_machine.rake +1 -0
  59. data/lib/tasks/state_machine.rb +27 -0
  60. data/test/files/en.yml +9 -0
  61. data/test/files/switch.rb +11 -0
  62. data/test/functional/state_machine_test.rb +980 -0
  63. data/test/test_helper.rb +4 -0
  64. data/test/unit/assertions_test.rb +40 -0
  65. data/test/unit/callback_test.rb +728 -0
  66. data/test/unit/condition_proxy_test.rb +328 -0
  67. data/test/unit/eval_helpers_test.rb +222 -0
  68. data/test/unit/event_collection_test.rb +324 -0
  69. data/test/unit/event_test.rb +795 -0
  70. data/test/unit/guard_test.rb +909 -0
  71. data/test/unit/integrations/active_model_test.rb +956 -0
  72. data/test/unit/integrations/active_record_test.rb +1918 -0
  73. data/test/unit/integrations/data_mapper_test.rb +1814 -0
  74. data/test/unit/integrations/mongo_mapper_test.rb +1382 -0
  75. data/test/unit/integrations/sequel_test.rb +1492 -0
  76. data/test/unit/integrations_test.rb +50 -0
  77. data/test/unit/invalid_event_test.rb +7 -0
  78. data/test/unit/invalid_transition_test.rb +7 -0
  79. data/test/unit/machine_collection_test.rb +565 -0
  80. data/test/unit/machine_test.rb +2349 -0
  81. data/test/unit/matcher_helpers_test.rb +37 -0
  82. data/test/unit/matcher_test.rb +155 -0
  83. data/test/unit/node_collection_test.rb +207 -0
  84. data/test/unit/state_collection_test.rb +280 -0
  85. data/test/unit/state_machine_test.rb +31 -0
  86. data/test/unit/state_test.rb +848 -0
  87. data/test/unit/transition_collection_test.rb +2098 -0
  88. data/test/unit/transition_test.rb +1384 -0
  89. metadata +176 -0
@@ -0,0 +1,1814 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../../test_helper')
2
+
3
+ # Load library
4
+ require 'rubygems'
5
+
6
+ gem 'dm-core', ENV['VERSION'] ? "=#{ENV['VERSION']}" : '>=0.9.4'
7
+ require 'dm-core'
8
+ require 'dm-core/version' unless defined?(::DataMapper::VERSION)
9
+
10
+ if Gem::Version.new(::DataMapper::VERSION) >= Gem::Version.new('0.10.3')
11
+ gem 'dm-migrations', ENV['VERSION'] ? "=#{ENV['VERSION']}" : '>=0.10.3'
12
+ require 'dm-migrations'
13
+ end
14
+
15
+ # Establish database connection
16
+ DataMapper.setup(:default, 'sqlite3::memory:')
17
+ DataObjects::Sqlite3.logger = DataObjects::Logger.new("#{File.dirname(__FILE__)}/../../data_mapper.log", :info)
18
+
19
+ module DataMapperTest
20
+ class BaseTestCase < Test::Unit::TestCase
21
+ def default_test
22
+ end
23
+
24
+ protected
25
+ # Creates a new DataMapper resource (and the associated table)
26
+ def new_resource(create_table = :foo, &block)
27
+ table_name = create_table || :foo
28
+
29
+ resource = Class.new
30
+ resource.class_eval do
31
+ include DataMapper::Resource
32
+
33
+ storage_names[:default] = table_name.to_s
34
+ def self.name; "DataMapperTest::#{storage_names[:default].capitalize}"; end
35
+
36
+ property :id, resource.class_eval('Serial')
37
+ property :state, String
38
+
39
+ auto_migrate! if create_table
40
+ end
41
+ resource.class_eval(&block) if block_given?
42
+ resource
43
+ end
44
+
45
+ # Creates a new DataMapper observer
46
+ def new_observer(resource, &block)
47
+ observer = Class.new do
48
+ include DataMapper::Observer
49
+ end
50
+ observer.observe(resource)
51
+ observer.class_eval(&block) if block_given?
52
+ observer
53
+ end
54
+ end
55
+
56
+ class IntegrationTest < BaseTestCase
57
+ def test_should_match_if_class_includes_data_mapper
58
+ assert StateMachine::Integrations::DataMapper.matches?(new_resource)
59
+ end
60
+
61
+ def test_should_not_match_if_class_does_not_include_data_mapper
62
+ assert !StateMachine::Integrations::DataMapper.matches?(Class.new)
63
+ end
64
+
65
+ def test_should_have_defaults
66
+ assert_equal e = {:action => :save, :use_transactions => false}, StateMachine::Integrations::DataMapper.defaults
67
+ end
68
+ end
69
+
70
+ class MachineWithoutDatabaseTest < BaseTestCase
71
+ def setup
72
+ @resource = new_resource(false) do
73
+ # Simulate the database not being available entirely
74
+ def self.repository
75
+ raise DataObjects::SyntaxError
76
+ end
77
+ end
78
+ end
79
+
80
+ def test_should_allow_machine_creation
81
+ assert_nothing_raised { StateMachine::Machine.new(@resource) }
82
+ end
83
+ end
84
+
85
+ class MachineUnmigratedTest < BaseTestCase
86
+ def setup
87
+ @resource = new_resource(false)
88
+ end
89
+
90
+ def test_should_allow_machine_creation
91
+ assert_nothing_raised { StateMachine::Machine.new(@resource) }
92
+ end
93
+ end
94
+
95
+ class MachineByDefaultTest < BaseTestCase
96
+ def setup
97
+ @resource = new_resource
98
+ @machine = StateMachine::Machine.new(@resource)
99
+ end
100
+
101
+ def test_should_use_save_as_action
102
+ assert_equal :save, @machine.action
103
+ end
104
+
105
+ def test_should_not_use_transactions
106
+ assert_equal false, @machine.use_transactions
107
+ end
108
+
109
+ def test_should_not_have_any_before_callbacks
110
+ assert_equal 0, @machine.callbacks[:before].size
111
+ end
112
+
113
+ def test_should_not_have_any_after_callbacks
114
+ assert_equal 0, @machine.callbacks[:after].size
115
+ end
116
+ end
117
+
118
+ class MachineWithStatesTest < BaseTestCase
119
+ def setup
120
+ @resource = new_resource
121
+ @machine = StateMachine::Machine.new(@resource)
122
+ @machine.state :first_gear
123
+ end
124
+
125
+ def test_should_humanize_name
126
+ assert_equal 'first gear', @machine.state(:first_gear).human_name
127
+ end
128
+ end
129
+
130
+ class MachineWithStaticInitialStateTest < BaseTestCase
131
+ def setup
132
+ @resource = new_resource do
133
+ attr_accessor :value
134
+ end
135
+ @machine = StateMachine::Machine.new(@resource, :initial => :parked)
136
+ end
137
+
138
+ def test_should_set_initial_state_on_created_object
139
+ record = @resource.new
140
+ assert_equal 'parked', record.state
141
+ end
142
+
143
+ def test_should_set_initial_state_with_nil_attributes
144
+ @resource.class_eval do
145
+ def attributes=(attributes)
146
+ super(attributes || {})
147
+ end
148
+ end
149
+
150
+ record = @resource.new(nil)
151
+ assert_equal 'parked', record.state
152
+ end
153
+
154
+ def test_should_still_set_attributes
155
+ record = @resource.new(:value => 1)
156
+ assert_equal 1, record.value
157
+ end
158
+
159
+ def test_should_not_allow_initialize_blocks
160
+ block_args = nil
161
+ record = @resource.new do |*args|
162
+ block_args = args
163
+ end
164
+
165
+ assert_nil block_args
166
+ end
167
+
168
+ def test_should_set_initial_state_before_setting_attributes
169
+ @resource.class_eval do
170
+ attr_accessor :state_during_setter
171
+
172
+ define_method(:value=) do |value|
173
+ self.state_during_setter = state
174
+ end
175
+ end
176
+
177
+ record = @resource.new(:value => 1)
178
+ assert_equal 'parked', record.state_during_setter
179
+ end
180
+
181
+ def test_should_not_set_initial_state_after_already_initialized
182
+ record = @resource.new(:value => 1)
183
+ assert_equal 'parked', record.state
184
+
185
+ record.state = 'idling'
186
+ record.attributes = {}
187
+ assert_equal 'idling', record.state
188
+ end
189
+
190
+ def test_should_use_stored_values_when_loading_from_database
191
+ @machine.state :idling
192
+
193
+ record = @resource.get(@resource.create(:state => 'idling').id)
194
+ assert_equal 'idling', record.state
195
+ end
196
+
197
+ def test_should_use_stored_values_when_loading_from_database_with_nil_state
198
+ @machine.state nil
199
+
200
+ record = @resource.get(@resource.create(:state => nil).id)
201
+ assert_nil record.state
202
+ end
203
+ end
204
+
205
+ class MachineWithDynamicInitialStateTest < BaseTestCase
206
+ def setup
207
+ @resource = new_resource do
208
+ attr_accessor :value
209
+ end
210
+ @machine = StateMachine::Machine.new(@resource, :initial => lambda {|object| :parked})
211
+ @machine.state :parked
212
+ end
213
+
214
+ def test_should_set_initial_state_on_created_object
215
+ record = @resource.new
216
+ assert_equal 'parked', record.state
217
+ end
218
+
219
+ def test_should_still_set_attributes
220
+ record = @resource.new(:value => 1)
221
+ assert_equal 1, record.value
222
+ end
223
+
224
+ def test_should_not_allow_initialize_blocks
225
+ block_args = nil
226
+ record = @resource.new do |*args|
227
+ block_args = args
228
+ end
229
+
230
+ assert_nil block_args
231
+ end
232
+
233
+ def test_should_set_initial_state_after_setting_attributes
234
+ @resource.class_eval do
235
+ attr_accessor :state_during_setter
236
+
237
+ define_method(:value=) do |value|
238
+ self.state_during_setter = state || 'nil'
239
+ end
240
+ end
241
+
242
+ record = @resource.new(:value => 1)
243
+ assert_equal 'nil', record.state_during_setter
244
+ end
245
+
246
+ def test_should_not_set_initial_state_after_already_initialized
247
+ record = @resource.new(:value => 1)
248
+ assert_equal 'parked', record.state
249
+
250
+ record.state = 'idling'
251
+ record.attributes = {}
252
+ assert_equal 'idling', record.state
253
+ end
254
+
255
+ def test_should_use_stored_values_when_loading_from_database
256
+ @machine.state :idling
257
+
258
+ record = @resource.get(@resource.create(:state => 'idling').id)
259
+ assert_equal 'idling', record.state
260
+ end
261
+
262
+ def test_should_use_stored_values_when_loading_from_database_with_nil_state
263
+ @machine.state nil
264
+
265
+ record = @resource.get(@resource.create(:state => nil).id)
266
+ assert_nil record.state
267
+ end
268
+ end
269
+
270
+ class MachineWithEventsTest < BaseTestCase
271
+ def setup
272
+ @resource = new_resource
273
+ @machine = StateMachine::Machine.new(@resource)
274
+ @machine.event :shift_up
275
+ end
276
+
277
+ def test_should_humanize_name
278
+ assert_equal 'shift up', @machine.event(:shift_up).human_name
279
+ end
280
+ end
281
+
282
+ class MachineWithColumnDefaultTest < BaseTestCase
283
+ def setup
284
+ @resource = new_resource do
285
+ property :status, String, :default => 'idling'
286
+ auto_migrate!
287
+ end
288
+ @machine = StateMachine::Machine.new(@resource, :status, :initial => :parked)
289
+ @record = @resource.new
290
+ end
291
+
292
+ def test_should_use_machine_default
293
+ assert_equal 'parked', @record.status
294
+ end
295
+ end
296
+
297
+ class MachineWithConflictingPredicateTest < BaseTestCase
298
+ def setup
299
+ @resource = new_resource do
300
+ def state?(*args)
301
+ true
302
+ end
303
+ end
304
+
305
+ @machine = StateMachine::Machine.new(@resource)
306
+ @record = @resource.new
307
+ end
308
+
309
+ def test_should_not_define_attribute_predicate
310
+ assert @record.state?
311
+ end
312
+ end
313
+
314
+ class MachineWithColumnStateAttributeTest < BaseTestCase
315
+ def setup
316
+ @resource = new_resource
317
+ @machine = StateMachine::Machine.new(@resource, :initial => :parked)
318
+ @machine.other_states(:idling)
319
+
320
+ @record = @resource.new
321
+ end
322
+
323
+ def test_should_not_override_the_column_reader
324
+ @record.attribute_set(:state, 'parked')
325
+ assert_equal 'parked', @record.state
326
+ end
327
+
328
+ def test_should_not_override_the_column_writer
329
+ @record.state = 'parked'
330
+ assert_equal 'parked', @record.attribute_get(:state)
331
+ end
332
+
333
+ def test_should_have_an_attribute_predicate
334
+ assert @record.respond_to?(:state?)
335
+ end
336
+
337
+ def test_should_raise_exception_for_predicate_without_parameters
338
+ assert_raise(IndexError) { @record.state? }
339
+ end
340
+
341
+ def test_should_return_false_for_predicate_if_does_not_match_current_value
342
+ assert !@record.state?(:idling)
343
+ end
344
+
345
+ def test_should_return_true_for_predicate_if_matches_current_value
346
+ assert @record.state?(:parked)
347
+ end
348
+
349
+ def test_should_raise_exception_for_predicate_if_invalid_state_specified
350
+ assert_raise(IndexError) { @record.state?(:invalid) }
351
+ end
352
+ end
353
+
354
+ class MachineWithNonColumnStateAttributeUndefinedTest < BaseTestCase
355
+ def setup
356
+ @resource = new_resource do
357
+ def initialize
358
+ # Skip attribute initialization
359
+ @initialized_state_machines = true
360
+ super
361
+ end
362
+ end
363
+
364
+ @machine = StateMachine::Machine.new(@resource, :status, :initial => 'parked')
365
+ @record = @resource.new
366
+ end
367
+
368
+ def test_should_define_a_new_property_for_the_attribute
369
+ assert_not_nil @resource.properties[:status]
370
+ end
371
+
372
+ def test_should_define_a_reader_attribute_for_the_attribute
373
+ assert @record.respond_to?(:status)
374
+ end
375
+
376
+ def test_should_define_a_writer_attribute_for_the_attribute
377
+ assert @record.respond_to?(:status=)
378
+ end
379
+
380
+ def test_should_define_an_attribute_predicate
381
+ assert @record.respond_to?(:status?)
382
+ end
383
+ end
384
+
385
+ class MachineWithNonColumnStateAttributeDefinedTest < BaseTestCase
386
+ def setup
387
+ @resource = new_resource do
388
+ attr_accessor :status
389
+ end
390
+
391
+ @machine = StateMachine::Machine.new(@resource, :status, :initial => :parked)
392
+ @machine.other_states(:idling)
393
+ @record = @resource.new
394
+ end
395
+
396
+ def test_should_return_false_for_predicate_if_does_not_match_current_value
397
+ assert !@record.status?(:idling)
398
+ end
399
+
400
+ def test_should_return_true_for_predicate_if_matches_current_value
401
+ assert @record.status?(:parked)
402
+ end
403
+
404
+ def test_should_raise_exception_for_predicate_if_invalid_state_specified
405
+ assert_raise(IndexError) { @record.status?(:invalid) }
406
+ end
407
+
408
+ def test_should_set_initial_state_on_created_object
409
+ assert_equal 'parked', @record.status
410
+ end
411
+ end
412
+
413
+ class MachineWithInitializedStateTest < BaseTestCase
414
+ def setup
415
+ @resource = new_resource
416
+ @machine = StateMachine::Machine.new(@resource, :initial => :parked)
417
+ @machine.state nil, :idling
418
+ end
419
+
420
+ def test_should_allow_nil_initial_state_when_static
421
+ record = @resource.new(:state => nil)
422
+ assert_nil record.state
423
+ end
424
+
425
+ def test_should_allow_nil_initial_state_when_dynamic
426
+ @machine.initial_state = lambda {:parked}
427
+ record = @resource.new(:state => nil)
428
+ assert_nil record.state
429
+ end
430
+
431
+ def test_should_allow_different_initial_state_when_static
432
+ record = @resource.new(:state => 'idling')
433
+ assert_equal 'idling', record.state
434
+ end
435
+
436
+ def test_should_allow_different_initial_state_when_dynamic
437
+ @machine.initial_state = lambda {:parked}
438
+ record = @resource.new(:state => 'idling')
439
+ assert_equal 'idling', record.state
440
+ end
441
+
442
+ if Gem::Version.new(::DataMapper::VERSION) >= Gem::Version.new('0.9.8')
443
+ def test_should_raise_exception_if_protected
444
+ @resource.class_eval do
445
+ protected :state=
446
+ end
447
+
448
+ assert_raise(ArgumentError) { @resource.new(:state => 'idling') }
449
+ end
450
+ end
451
+ end
452
+
453
+ class MachineWithLoopbackTest < BaseTestCase
454
+ def setup
455
+ @resource = new_resource do
456
+ property :updated_at, DateTime
457
+ auto_migrate!
458
+
459
+ # Simulate dm-timestamps
460
+ before :update do
461
+ return unless dirty?
462
+ self.updated_at = DateTime.now
463
+ end
464
+ end
465
+
466
+ @machine = StateMachine::Machine.new(@resource, :initial => :parked)
467
+ @machine.event :park
468
+
469
+ @record = @resource.create(:updated_at => Time.now - 1)
470
+ @transition = StateMachine::Transition.new(@record, @machine, :park, :parked, :parked)
471
+
472
+ @timestamp = @record.updated_at
473
+ @transition.perform
474
+ end
475
+
476
+ def test_should_update_record
477
+ assert_not_equal @timestamp, @record.updated_at
478
+ end
479
+ end
480
+
481
+ class MachineWithDirtyAttributesTest < BaseTestCase
482
+ def setup
483
+ @resource = new_resource
484
+ @machine = StateMachine::Machine.new(@resource, :initial => :parked)
485
+ @machine.event :ignite
486
+ @machine.state :idling
487
+
488
+ @record = @resource.create
489
+
490
+ @transition = StateMachine::Transition.new(@record, @machine, :ignite, :parked, :idling)
491
+ @transition.perform(false)
492
+ end
493
+
494
+ def test_should_include_state_in_changed_attributes
495
+ assert_equal e = {@resource.properties[:state] => 'idling'}, @record.dirty_attributes
496
+ end
497
+
498
+ def test_should_track_attribute_change
499
+ if Gem::Version.new(::DataMapper::VERSION) >= Gem::Version.new('0.10.0')
500
+ assert_equal e = {@resource.properties[:state] => 'parked'}, @record.original_attributes
501
+ else
502
+ assert_equal e = {:state => 'parked'}, @record.original_values
503
+ end
504
+ end
505
+
506
+ def test_should_not_reset_changes_on_multiple_transitions
507
+ transition = StateMachine::Transition.new(@record, @machine, :ignite, :idling, :idling)
508
+ transition.perform(false)
509
+
510
+ if Gem::Version.new(::DataMapper::VERSION) >= Gem::Version.new('0.10.0')
511
+ assert_equal e = {@resource.properties[:state] => 'parked'}, @record.original_attributes
512
+ else
513
+ assert_equal e = {:state => 'parked'}, @record.original_values
514
+ end
515
+ end
516
+
517
+ def test_should_have_changes_when_loaded_from_database
518
+ record = @resource.get(@record.id)
519
+ assert record.dirty_attributes.blank?
520
+ end
521
+ end
522
+
523
+ class MachineWithDirtyAttributesDuringLoopbackTest < BaseTestCase
524
+ def setup
525
+ @resource = new_resource
526
+ @machine = StateMachine::Machine.new(@resource, :initial => :parked)
527
+ @machine.event :park
528
+
529
+ @record = @resource.create
530
+
531
+ @transition = StateMachine::Transition.new(@record, @machine, :park, :parked, :parked)
532
+ @transition.perform(false)
533
+ end
534
+
535
+ def test_should_include_state_in_changed_attributes
536
+ assert_equal e = {@resource.properties[:state] => 'parked'}, @record.dirty_attributes
537
+ end
538
+
539
+ def test_should_track_attribute_change
540
+ if Gem::Version.new(::DataMapper::VERSION) >= Gem::Version.new('0.10.3')
541
+ assert_equal e = {@resource.properties[:state] => 'parked'}, @record.original_attributes
542
+ elsif Gem::Version.new(::DataMapper::VERSION) >= Gem::Version.new('0.10.0')
543
+ assert_equal e = {@resource.properties[:state] => 'parked-ignored'}, @record.original_attributes
544
+ else
545
+ assert_equal e = {:state => 'parked-ignored'}, @record.original_values
546
+ end
547
+ end
548
+ end
549
+
550
+ class MachineWithDirtyAttributesAndCustomAttributeTest < BaseTestCase
551
+ def setup
552
+ @resource = new_resource do
553
+ property :status, String, :default => 'idling'
554
+ auto_migrate!
555
+ end
556
+ @machine = StateMachine::Machine.new(@resource, :status, :initial => :parked)
557
+ @machine.event :ignite
558
+ @machine.state :idling
559
+
560
+ @record = @resource.create
561
+
562
+ @transition = StateMachine::Transition.new(@record, @machine, :ignite, :parked, :idling)
563
+ @transition.perform(false)
564
+ end
565
+
566
+ def test_should_include_state_in_changed_attributes
567
+ assert_equal e = {@resource.properties[:status] => 'idling'}, @record.dirty_attributes
568
+ end
569
+
570
+ def test_should_track_attribute_change
571
+ if Gem::Version.new(::DataMapper::VERSION) >= Gem::Version.new('0.10.0')
572
+ assert_equal e = {@resource.properties[:status] => 'parked'}, @record.original_attributes
573
+ else
574
+ assert_equal e = {:status => 'parked'}, @record.original_values
575
+ end
576
+ end
577
+
578
+ def test_should_not_reset_changes_on_multiple_transitions
579
+ transition = StateMachine::Transition.new(@record, @machine, :ignite, :idling, :idling)
580
+ transition.perform(false)
581
+
582
+ if Gem::Version.new(::DataMapper::VERSION) >= Gem::Version.new('0.10.0')
583
+ assert_equal e = {@resource.properties[:status] => 'parked'}, @record.original_attributes
584
+ else
585
+ assert_equal e = {:status => 'parked'}, @record.original_values
586
+ end
587
+ end
588
+ end
589
+
590
+ class MachineWithDirtyAttributeAndCustomAttributesDuringLoopbackTest < BaseTestCase
591
+ def setup
592
+ @resource = new_resource do
593
+ property :status, String, :default => 'idling'
594
+ auto_migrate!
595
+ end
596
+ @machine = StateMachine::Machine.new(@resource, :status, :initial => :parked)
597
+ @machine.event :park
598
+
599
+ @record = @resource.create
600
+
601
+ @transition = StateMachine::Transition.new(@record, @machine, :park, :parked, :parked)
602
+ @transition.perform(false)
603
+ end
604
+
605
+ def test_should_include_state_in_changed_attributes
606
+ assert_equal e = {@resource.properties[:status] => 'parked'}, @record.dirty_attributes
607
+ end
608
+
609
+ def test_should_track_attribute_changes
610
+ if Gem::Version.new(::DataMapper::VERSION) >= Gem::Version.new('0.10.3')
611
+ assert_equal e = {@resource.properties[:status] => 'parked'}, @record.original_attributes
612
+ elsif Gem::Version.new(::DataMapper::VERSION) >= Gem::Version.new('0.10.0')
613
+ assert_equal e = {@resource.properties[:status] => 'parked-ignored'}, @record.original_attributes
614
+ else
615
+ assert_equal e = {:status => 'parked-ignored'}, @record.original_values
616
+ end
617
+ end
618
+ end
619
+
620
+ class MachineWithoutTransactionsTest < BaseTestCase
621
+ def setup
622
+ @resource = new_resource
623
+ @machine = StateMachine::Machine.new(@resource, :use_transactions => false)
624
+ end
625
+
626
+ def test_should_not_rollback_transaction_if_false
627
+ @machine.within_transaction(@resource.new) do
628
+ @resource.create
629
+ false
630
+ end
631
+
632
+ assert_equal 1, @resource.all.size
633
+ end
634
+
635
+ def test_should_not_rollback_transaction_if_true
636
+ @machine.within_transaction(@resource.new) do
637
+ @resource.create
638
+ true
639
+ end
640
+
641
+ assert_equal 1, @resource.all.size
642
+ end
643
+ end
644
+
645
+ begin
646
+ if Gem::Version.new(::DataMapper::VERSION) >= Gem::Version.new('0.10.3')
647
+ gem 'dm-transactions', ENV['VERSION'] ? "=#{ENV['VERSION']}" : '>=0.10.3'
648
+ require 'dm-transactions'
649
+ end
650
+
651
+ class MachineWithTransactionsTest < BaseTestCase
652
+ def setup
653
+ @resource = new_resource
654
+ @machine = StateMachine::Machine.new(@resource, :use_transactions => true)
655
+ end
656
+
657
+ def test_should_rollback_transaction_if_false
658
+ @machine.within_transaction(@resource.new) do
659
+ @resource.create
660
+ false
661
+ end
662
+
663
+ assert_equal 0, @resource.all.size
664
+ end
665
+
666
+ def test_should_not_rollback_transaction_if_true
667
+ @machine.within_transaction(@resource.new) do
668
+ @resource.create
669
+ true
670
+ end
671
+
672
+ assert_equal 1, @resource.all.size
673
+ end
674
+ end
675
+ rescue LoadError
676
+ $stderr.puts "Skipping DataMapper Transaction tests. `gem install dm-transactions#{" -v #{ENV['VERSION']}" if ENV['VERSION']}` and try again."
677
+ end
678
+
679
+ class MachineWithCallbacksTest < BaseTestCase
680
+ def setup
681
+ @resource = new_resource
682
+ @machine = StateMachine::Machine.new(@resource)
683
+ @machine.state :parked, :idling
684
+ @machine.event :ignite
685
+
686
+ @record = @resource.new(:state => 'parked')
687
+ @transition = StateMachine::Transition.new(@record, @machine, :ignite, :parked, :idling)
688
+ end
689
+
690
+ def test_should_run_before_callbacks
691
+ called = false
692
+ @machine.before_transition {called = true}
693
+
694
+ @transition.perform
695
+ assert called
696
+ end
697
+
698
+ def test_should_pass_transition_to_before_callbacks_with_one_argument
699
+ transition = nil
700
+ @machine.before_transition {|arg| transition = arg}
701
+
702
+ @transition.perform
703
+ assert_equal @transition, transition
704
+ end
705
+
706
+ def test_should_pass_transition_to_before_callbacks_with_multiple_arguments
707
+ callback_args = nil
708
+ @machine.before_transition {|*args| callback_args = args}
709
+
710
+ @transition.perform
711
+ assert_equal [@transition], callback_args
712
+ end
713
+
714
+ def test_should_run_before_callbacks_within_the_context_of_the_record
715
+ context = nil
716
+ @machine.before_transition {context = self}
717
+
718
+ @transition.perform
719
+ assert_equal @record, context
720
+ end
721
+
722
+ def test_should_run_after_callbacks
723
+ called = false
724
+ @machine.after_transition {called = true}
725
+
726
+ @transition.perform
727
+ assert called
728
+ end
729
+
730
+ def test_should_pass_transition_to_after_callbacks_with_multiple_arguments
731
+ callback_args = nil
732
+ @machine.after_transition {|*args| callback_args = args}
733
+
734
+ @transition.perform
735
+ assert_equal [@transition], callback_args
736
+ end
737
+
738
+ def test_should_run_after_callbacks_with_the_context_of_the_record
739
+ context = nil
740
+ @machine.after_transition {context = self}
741
+
742
+ @transition.perform
743
+ assert_equal @record, context
744
+ end
745
+
746
+ def test_should_run_around_callbacks
747
+ before_called = false
748
+ after_called = [false]
749
+ @machine.around_transition {|block| before_called = true; block.call; after_called[0] = true}
750
+
751
+ @transition.perform
752
+ assert before_called
753
+ assert after_called[0]
754
+ end
755
+
756
+ def test_should_run_around_callbacks_with_the_context_of_the_record
757
+ context = nil
758
+ @machine.around_transition {|block| context = self; block.call}
759
+
760
+ @transition.perform
761
+ assert_equal @record, context
762
+ end
763
+
764
+ def test_should_allow_symbolic_callbacks
765
+ callback_args = nil
766
+
767
+ klass = class << @record; self; end
768
+ klass.send(:define_method, :after_ignite) do |*args|
769
+ callback_args = args
770
+ end
771
+
772
+ @machine.before_transition(:after_ignite)
773
+
774
+ @transition.perform
775
+ assert_equal [@transition], callback_args
776
+ end
777
+
778
+ def test_should_allow_string_callbacks
779
+ class << @record
780
+ attr_reader :callback_result
781
+ end
782
+
783
+ @machine.before_transition('@callback_result = [1, 2, 3]')
784
+ @transition.perform
785
+
786
+ assert_equal [1, 2, 3], @record.callback_result
787
+ end
788
+ end
789
+
790
+ class MachineWithFailedBeforeCallbacksTest < BaseTestCase
791
+ def setup
792
+ callbacks = []
793
+
794
+ @resource = new_resource
795
+ @machine = StateMachine::Machine.new(@resource)
796
+ @machine.state :parked, :idling
797
+ @machine.event :ignite
798
+ @machine.before_transition {callbacks << :before_1; throw :halt}
799
+ @machine.before_transition {callbacks << :before_2}
800
+ @machine.after_transition {callbacks << :after}
801
+ @machine.around_transition {|block| callbacks << :around_before; block.call; callbacks << :around_after}
802
+
803
+ @record = @resource.new(:state => 'parked')
804
+ @transition = StateMachine::Transition.new(@record, @machine, :ignite, :parked, :idling)
805
+ @result = @transition.perform
806
+
807
+ @callbacks = callbacks
808
+ end
809
+
810
+ def test_should_not_be_successful
811
+ assert !@result
812
+ end
813
+
814
+ def test_should_not_change_current_state
815
+ assert_equal 'parked', @record.state
816
+ end
817
+
818
+ def test_should_not_run_action
819
+ assert @record.respond_to?(:new?) ? @record.new? : @record.new_record?
820
+ end
821
+
822
+ def test_should_not_run_further_callbacks
823
+ assert_equal [:before_1], @callbacks
824
+ end
825
+ end
826
+
827
+ class MachineWithFailedActionTest < BaseTestCase
828
+ def setup
829
+ @resource = new_resource do
830
+ before(:create) { throw :halt }
831
+ end
832
+
833
+ @machine = StateMachine::Machine.new(@resource)
834
+ @machine.state :parked, :idling
835
+ @machine.event :ignite
836
+
837
+ callbacks = []
838
+ @machine.before_transition {callbacks << :before}
839
+ @machine.after_transition {callbacks << :after}
840
+ @machine.after_transition(:include_failures => true) {callbacks << :after_failure}
841
+ @machine.around_transition {|block| callbacks << :around_before; block.call; callbacks << :around_after}
842
+ @machine.around_transition(:include_failures => true) do |block|
843
+ callbacks << :around_before_failure
844
+ block.call
845
+ callbacks << :around_after_failure
846
+ end
847
+
848
+ @record = @resource.new(:state => 'parked')
849
+ @transition = StateMachine::Transition.new(@record, @machine, :ignite, :parked, :idling)
850
+ @result = @transition.perform
851
+
852
+ @callbacks = callbacks
853
+ end
854
+
855
+ def test_should_not_be_successful
856
+ assert !@result
857
+ end
858
+
859
+ def test_should_not_change_current_state
860
+ assert_equal 'parked', @record.state
861
+ end
862
+
863
+ def test_should_not_save_record
864
+ assert @record.respond_to?(:new?) ? @record.new? : @record.new_record?
865
+ end
866
+
867
+ def test_should_run_before_callbacks_and_after_callbacks_with_failures
868
+ assert_equal [:before, :around_before, :around_before_failure, :around_after_failure, :after_failure], @callbacks
869
+ end
870
+ end
871
+
872
+ class MachineWithFailedAfterCallbacksTest < BaseTestCase
873
+ def setup
874
+ callbacks = []
875
+
876
+ @resource = new_resource
877
+ @machine = StateMachine::Machine.new(@resource)
878
+ @machine.state :parked, :idling
879
+ @machine.event :ignite
880
+ @machine.after_transition {callbacks << :after_1; throw :halt}
881
+ @machine.after_transition {callbacks << :after_2}
882
+ @machine.around_transition {|block| callbacks << :around_before; block.call; callbacks << :around_after}
883
+
884
+ @record = @resource.new(:state => 'parked')
885
+ @transition = StateMachine::Transition.new(@record, @machine, :ignite, :parked, :idling)
886
+ @result = @transition.perform
887
+
888
+ @callbacks = callbacks
889
+ end
890
+
891
+ def test_should_be_successful
892
+ assert @result
893
+ end
894
+
895
+ def test_should_change_current_state
896
+ assert_equal 'idling', @record.state
897
+ end
898
+
899
+ def test_should_save_record
900
+ assert !(@record.respond_to?(:new?) ? @record.new? : @record.new_record?)
901
+ end
902
+
903
+ def test_should_not_run_further_after_callbacks
904
+ assert_equal [:around_before, :around_after, :after_1], @callbacks
905
+ end
906
+ end
907
+
908
+ begin
909
+ gem 'dm-validations', ENV['VERSION'] ? "=#{ENV['VERSION']}" : '>=0.9.4'
910
+ require 'dm-validations'
911
+
912
+ class MachineWithValidationsTest < BaseTestCase
913
+ def setup
914
+ @resource = new_resource
915
+ @machine = StateMachine::Machine.new(@resource)
916
+ @machine.state :parked
917
+
918
+ @record = @resource.new
919
+ end
920
+
921
+ def test_should_invalidate_using_errors
922
+ @record.state = 'parked'
923
+
924
+ @machine.invalidate(@record, :state, :invalid_transition, [[:event, 'park']])
925
+ assert_equal ['cannot transition via "park"'], @record.errors.on(:state)
926
+ end
927
+
928
+ def test_should_auto_prefix_custom_attributes_on_invalidation
929
+ @machine.invalidate(@record, :event, :invalid)
930
+
931
+ assert_equal ['is invalid'], @record.errors.on(:state_event)
932
+ end
933
+
934
+ def test_should_clear_errors_on_reset
935
+ @record.state = 'parked'
936
+ @record.errors.add(:state, 'is invalid')
937
+
938
+ @machine.reset(@record)
939
+ assert_nil @record.errors.on(:id)
940
+ end
941
+
942
+ def test_should_be_valid_if_state_is_known
943
+ @record.state = 'parked'
944
+
945
+ assert @record.valid?
946
+ end
947
+
948
+ def test_should_not_be_valid_if_state_is_unknown
949
+ @record.state = 'invalid'
950
+
951
+ assert !@record.valid?
952
+ assert_equal ['is invalid'], @record.errors.on(:state)
953
+ end
954
+ end
955
+
956
+ class MachineWithValidationsAndCustomAttributeTest < BaseTestCase
957
+ def setup
958
+ @resource = new_resource
959
+ @machine = StateMachine::Machine.new(@resource, :status, :attribute => :state)
960
+ @machine.state :parked
961
+
962
+ @record = @resource.new
963
+ end
964
+
965
+ def test_should_add_validation_errors_to_custom_attribute
966
+ @record.state = 'invalid'
967
+
968
+ assert !@record.valid?
969
+ assert_equal ['is invalid'], @record.errors.on(:state)
970
+
971
+ @record.state = 'parked'
972
+ assert @record.valid?
973
+ end
974
+ end
975
+
976
+ class MachineWithStateDrivenValidationsTest < BaseTestCase
977
+ def setup
978
+ @resource = resource = new_resource do
979
+ attr_accessor :seatbelt
980
+ end
981
+
982
+ @machine = StateMachine::Machine.new(@resource)
983
+ @machine.state :first_gear, :second_gear do
984
+ if resource.respond_to?(:validates_presence_of)
985
+ validates_presence_of :seatbelt
986
+ else
987
+ validates_present :seatbelt
988
+ end
989
+ end
990
+ @machine.other_states :parked
991
+ end
992
+
993
+ def test_should_be_valid_if_validation_fails_outside_state_scope
994
+ record = @resource.new(:state => 'parked', :seatbelt => nil)
995
+ assert record.valid?
996
+ end
997
+
998
+ def test_should_be_invalid_if_validation_fails_within_state_scope
999
+ record = @resource.new(:state => 'first_gear', :seatbelt => nil)
1000
+ assert !record.valid?
1001
+ end
1002
+
1003
+ def test_should_be_valid_if_validation_succeeds_within_state_scope
1004
+ record = @resource.new(:state => 'second_gear', :seatbelt => true)
1005
+ assert record.valid?
1006
+ end
1007
+ end
1008
+
1009
+ # See README caveats
1010
+ if Gem::Version.new(::DataMapper::VERSION) > Gem::Version.new('0.9.6')
1011
+ class MachineWithEventAttributesOnValidationTest < BaseTestCase
1012
+ def setup
1013
+ @resource = new_resource
1014
+ @machine = StateMachine::Machine.new(@resource)
1015
+ @machine.event :ignite do
1016
+ transition :parked => :idling
1017
+ end
1018
+
1019
+ @record = @resource.new
1020
+ @record.state = 'parked'
1021
+ @record.state_event = 'ignite'
1022
+ end
1023
+
1024
+ def test_should_fail_if_event_is_invalid
1025
+ @record.state_event = 'invalid'
1026
+ assert !@record.valid?
1027
+ assert_equal ['is invalid'], @record.errors.full_messages
1028
+ end
1029
+
1030
+ def test_should_fail_if_event_has_no_transition
1031
+ @record.state = 'idling'
1032
+ assert !@record.valid?
1033
+ assert_equal ['cannot transition when idling'], @record.errors.full_messages
1034
+ end
1035
+
1036
+ def test_should_be_successful_if_event_has_transition
1037
+ assert @record.valid?
1038
+ end
1039
+
1040
+ def test_should_run_before_callbacks
1041
+ ran_callback = false
1042
+ @machine.before_transition { ran_callback = true }
1043
+
1044
+ @record.valid?
1045
+ assert ran_callback
1046
+ end
1047
+
1048
+ def test_should_run_around_callbacks_before_yield
1049
+ ran_callback = false
1050
+ @machine.around_transition {|block| ran_callback = true; block.call }
1051
+
1052
+ @record.valid?
1053
+ assert ran_callback
1054
+ end
1055
+
1056
+ def test_should_persist_new_state
1057
+ @record.valid?
1058
+ assert_equal 'idling', @record.state
1059
+ end
1060
+
1061
+ def test_should_not_run_after_callbacks
1062
+ ran_callback = false
1063
+ @machine.after_transition { ran_callback = true }
1064
+
1065
+ @record.valid?
1066
+ assert !ran_callback
1067
+ end
1068
+
1069
+ def test_should_not_run_after_callbacks_with_failures_disabled_if_validation_fails
1070
+ @resource.class_eval do
1071
+ attr_accessor :seatbelt
1072
+ if respond_to?(:validates_presence_of)
1073
+ validates_presence_of :seatbelt
1074
+ else
1075
+ validates_present :seatbelt
1076
+ end
1077
+ end
1078
+
1079
+ ran_callback = false
1080
+ @machine.after_transition { ran_callback = true }
1081
+
1082
+ @record.valid?
1083
+ assert !ran_callback
1084
+ end
1085
+
1086
+ def test_should_run_after_callbacks_with_failures_enabled_if_validation_fails
1087
+ @resource.class_eval do
1088
+ attr_accessor :seatbelt
1089
+ if respond_to?(:validates_presence_of)
1090
+ validates_presence_of :seatbelt
1091
+ else
1092
+ validates_present :seatbelt
1093
+ end
1094
+ end
1095
+
1096
+ ran_callback = false
1097
+ @machine.after_transition(:include_failures => true) { ran_callback = true }
1098
+
1099
+ @record.valid?
1100
+ assert ran_callback
1101
+ end
1102
+
1103
+ def test_should_not_run_around_callbacks_after_yield
1104
+ ran_callback = [false]
1105
+ @machine.around_transition {|block| block.call; ran_callback[0] = true }
1106
+
1107
+ @record.valid?
1108
+ assert !ran_callback[0]
1109
+ end
1110
+
1111
+ def test_should_not_run_around_callbacks_after_yield_with_failures_disabled_if_validation_fails
1112
+ @resource.class_eval do
1113
+ attr_accessor :seatbelt
1114
+ if respond_to?(:validates_presence_of)
1115
+ validates_presence_of :seatbelt
1116
+ else
1117
+ validates_present :seatbelt
1118
+ end
1119
+ end
1120
+
1121
+ ran_callback = [false]
1122
+ @machine.around_transition {|block| block.call; ran_callback[0] = true }
1123
+
1124
+ @record.valid?
1125
+ assert !ran_callback[0]
1126
+ end
1127
+
1128
+ def test_should_run_around_callbacks_after_yield_with_failures_enabled_if_validation_fails
1129
+ @resource.class_eval do
1130
+ attr_accessor :seatbelt
1131
+ if respond_to?(:validates_presence_of)
1132
+ validates_presence_of :seatbelt
1133
+ else
1134
+ validates_present :seatbelt
1135
+ end
1136
+ end
1137
+
1138
+ ran_callback = [false]
1139
+ @machine.around_transition(:include_failures => true) {|block| block.call; ran_callback[0] = true }
1140
+
1141
+ @record.valid?
1142
+ assert ran_callback[0]
1143
+ end
1144
+
1145
+ def test_should_not_run_before_transitions_within_transaction
1146
+ @machine.before_transition { self.class.create; throw :halt }
1147
+
1148
+ assert !@record.valid?
1149
+ assert_equal 1, @resource.all.size
1150
+ end
1151
+ end
1152
+
1153
+ class MachineWithEventAttributesOnSaveTest < BaseTestCase
1154
+ def setup
1155
+ @resource = new_resource
1156
+ @machine = StateMachine::Machine.new(@resource)
1157
+ @machine.event :ignite do
1158
+ transition :parked => :idling
1159
+ end
1160
+
1161
+ @record = @resource.new
1162
+ @record.state = 'parked'
1163
+ @record.state_event = 'ignite'
1164
+ end
1165
+
1166
+ def test_should_fail_if_event_is_invalid
1167
+ @record.state_event = 'invalid'
1168
+ assert !@record.save
1169
+ end
1170
+
1171
+ def test_should_fail_if_event_has_no_transition
1172
+ @record.state = 'idling'
1173
+ assert !@record.save
1174
+ end
1175
+
1176
+ def test_should_be_successful_if_event_has_transition
1177
+ assert_equal true, @record.save
1178
+ end
1179
+
1180
+ def test_should_run_before_callbacks
1181
+ ran_callback = false
1182
+ @machine.before_transition { ran_callback = true }
1183
+
1184
+ @record.save
1185
+ assert ran_callback
1186
+ end
1187
+
1188
+ def test_should_run_before_callbacks_once
1189
+ before_count = 0
1190
+ @machine.before_transition { before_count += 1 }
1191
+
1192
+ @record.save
1193
+ assert_equal 1, before_count
1194
+ end
1195
+
1196
+ def test_should_run_around_callbacks_before_yield
1197
+ ran_callback = false
1198
+ @machine.around_transition {|block| ran_callback = true; block.call }
1199
+
1200
+ @record.save
1201
+ assert ran_callback
1202
+ end
1203
+
1204
+ def test_should_run_around_callbacks_before_yield_once
1205
+ around_before_count = 0
1206
+ @machine.around_transition {|block| around_before_count += 1; block.call }
1207
+
1208
+ @record.save
1209
+ assert_equal 1, around_before_count
1210
+ end
1211
+
1212
+ def test_should_persist_new_state
1213
+ @record.save
1214
+ assert_equal 'idling', @record.state
1215
+ end
1216
+
1217
+ def test_should_run_after_callbacks
1218
+ ran_callback = false
1219
+ @machine.after_transition { ran_callback = true }
1220
+
1221
+ @record.save
1222
+ assert ran_callback
1223
+ end
1224
+
1225
+ def test_should_not_run_after_callbacks_with_failures_disabled_if_fails
1226
+ @resource.before(:create) { throw :halt }
1227
+
1228
+ ran_callback = false
1229
+ @machine.after_transition { ran_callback = true }
1230
+
1231
+ @record.save
1232
+ assert !ran_callback
1233
+ end
1234
+
1235
+ def test_should_run_after_callbacks_with_failures_enabled_if_fails
1236
+ @resource.before(:create) { throw :halt }
1237
+
1238
+ ran_callback = false
1239
+ @machine.after_transition(:include_failures => true) { ran_callback = true }
1240
+
1241
+ @record.save
1242
+ assert ran_callback
1243
+ end
1244
+
1245
+ def test_should_not_run_around_callbacks_with_failures_disabled_if_fails
1246
+ @resource.before(:create) { throw :halt }
1247
+
1248
+ ran_callback = [false]
1249
+ @machine.around_transition {|block| block.call; ran_callback[0] = true }
1250
+
1251
+ @record.save
1252
+ assert !ran_callback[0]
1253
+ end
1254
+
1255
+ def test_should_run_around_callbacks_after_yield
1256
+ ran_callback = [false]
1257
+ @machine.around_transition {|block| block.call; ran_callback[0] = true }
1258
+
1259
+ @record.save
1260
+ assert ran_callback[0]
1261
+ end
1262
+
1263
+ def test_should_run_around_callbacks_after_yield_with_failures_enabled_if_fails
1264
+ @resource.before(:create) { throw :halt }
1265
+
1266
+ ran_callback = [false]
1267
+ @machine.around_transition(:include_failures => true) {|block| block.call; ran_callback[0] = true }
1268
+
1269
+ @record.save
1270
+ assert ran_callback[0]
1271
+ end
1272
+
1273
+ def test_should_not_run_before_transitions_within_transaction
1274
+ @machine.before_transition { self.class.create; throw :halt }
1275
+
1276
+ assert_equal false, @record.save
1277
+ assert_equal 1, @resource.all.size
1278
+ end
1279
+
1280
+ def test_should_not_run_after_transitions_within_transaction
1281
+ @machine.before_transition { self.class.create; throw :halt }
1282
+
1283
+ assert_equal false, @record.save
1284
+ assert_equal 1, @resource.all.size
1285
+ end
1286
+
1287
+ def test_should_not_run_around_transition_within_transaction
1288
+ @machine.around_transition { self.class.create; throw :halt }
1289
+
1290
+ assert_equal false, @record.save
1291
+ assert_equal 1, @resource.all.size
1292
+ end
1293
+ end
1294
+ end
1295
+
1296
+ if Gem::Version.new(::DataMapper::VERSION) >= Gem::Version.new('0.10.0')
1297
+ class MachineWithEventAttributesOnSaveBangTest < BaseTestCase
1298
+ def setup
1299
+ @resource = new_resource
1300
+ @machine = StateMachine::Machine.new(@resource)
1301
+ @machine.event :ignite do
1302
+ transition :parked => :idling
1303
+ end
1304
+
1305
+ @record = @resource.new
1306
+ @record.state = 'parked'
1307
+ @record.state_event = 'ignite'
1308
+ end
1309
+
1310
+ def test_should_fail_if_event_is_invalid
1311
+ @record.state_event = 'invalid'
1312
+ assert !@record.save!
1313
+ end
1314
+
1315
+ def test_should_fail_if_event_has_no_transition
1316
+ @record.state = 'idling'
1317
+ assert !@record.save!
1318
+ end
1319
+
1320
+ def test_should_be_successful_if_event_has_transition
1321
+ assert_equal true, @record.save!
1322
+ end
1323
+
1324
+ def test_should_run_before_callbacks
1325
+ ran_callback = false
1326
+ @machine.before_transition { ran_callback = true }
1327
+
1328
+ @record.save!
1329
+ assert ran_callback
1330
+ end
1331
+
1332
+ def test_should_run_before_callbacks_once
1333
+ before_count = 0
1334
+ @machine.before_transition { before_count += 1 }
1335
+
1336
+ @record.save!
1337
+ assert_equal 1, before_count
1338
+ end
1339
+
1340
+ def test_should_run_around_callbacks_before_yield
1341
+ ran_callback = false
1342
+ @machine.around_transition {|block| ran_callback = true; block.call }
1343
+
1344
+ @record.save!
1345
+ assert ran_callback
1346
+ end
1347
+
1348
+ def test_should_run_around_callbacks_before_yield_once
1349
+ around_before_count = 0
1350
+ @machine.around_transition {|block| around_before_count += 1; block.call }
1351
+
1352
+ @record.save!
1353
+ assert_equal 1, around_before_count
1354
+ end
1355
+
1356
+ def test_should_persist_new_state
1357
+ @record.save!
1358
+ assert_equal 'idling', @record.state
1359
+ end
1360
+
1361
+ def test_should_run_after_callbacks
1362
+ ran_callback = false
1363
+ @machine.after_transition { ran_callback = true }
1364
+
1365
+ @record.save!
1366
+ assert ran_callback
1367
+ end
1368
+
1369
+ def test_should_run_around_callbacks_after_yield
1370
+ ran_callback = [false]
1371
+ @machine.around_transition {|block| block.call; ran_callback[0] = true }
1372
+
1373
+ @record.save!
1374
+ assert ran_callback[0]
1375
+ end
1376
+ end
1377
+ end
1378
+
1379
+ if Gem::Version.new(::DataMapper::VERSION) > Gem::Version.new('0.9.6')
1380
+ class MachineWithEventAttributesOnCustomActionTest < BaseTestCase
1381
+ def setup
1382
+ @superclass = new_resource do
1383
+ def persist
1384
+ save
1385
+ end
1386
+ end
1387
+ @resource = Class.new(@superclass)
1388
+ @machine = StateMachine::Machine.new(@resource, :action => :persist)
1389
+ @machine.event :ignite do
1390
+ transition :parked => :idling
1391
+ end
1392
+
1393
+ @record = @resource.new
1394
+ @record.state = 'parked'
1395
+ @record.state_event = 'ignite'
1396
+ end
1397
+
1398
+ def test_should_not_transition_on_valid?
1399
+ @record.valid?
1400
+ assert_equal 'parked', @record.state
1401
+ end
1402
+
1403
+ def test_should_not_transition_on_save
1404
+ @record.save
1405
+ assert_equal 'parked', @record.state
1406
+ end
1407
+
1408
+ def test_should_transition_on_custom_action
1409
+ @record.persist
1410
+ assert_equal 'idling', @record.state
1411
+ end
1412
+ end
1413
+ end
1414
+ rescue LoadError
1415
+ $stderr.puts "Skipping DataMapper Validation tests. `gem install dm-validations#{" -v #{ENV['VERSION']}" if ENV['VERSION']}` and try again."
1416
+ end
1417
+
1418
+ begin
1419
+ gem 'dm-observer', ENV['VERSION'] ? "=#{ENV['VERSION']}" : '>=0.9.4'
1420
+ require 'dm-observer'
1421
+
1422
+ class MachineWithObserversTest < BaseTestCase
1423
+ def setup
1424
+ @resource = new_resource
1425
+ @machine = StateMachine::Machine.new(@resource)
1426
+ @machine.state :parked, :idling
1427
+ @machine.event :ignite
1428
+ @record = @resource.new(:state => 'parked')
1429
+ @transition = StateMachine::Transition.new(@record, @machine, :ignite, :parked, :idling)
1430
+ end
1431
+
1432
+ def test_should_provide_matcher_helpers
1433
+ matchers = []
1434
+
1435
+ new_observer(@resource) do
1436
+ matchers = [all, any, same]
1437
+ end
1438
+
1439
+ assert_equal [StateMachine::AllMatcher.instance, StateMachine::AllMatcher.instance, StateMachine::LoopbackMatcher.instance], matchers
1440
+ end
1441
+
1442
+ def test_should_call_before_transition_callback_if_requirements_match
1443
+ called = false
1444
+
1445
+ observer = new_observer(@resource) do
1446
+ before_transition :from => :parked do
1447
+ called = true
1448
+ end
1449
+ end
1450
+
1451
+ @transition.perform
1452
+ assert called
1453
+ end
1454
+
1455
+ def test_should_not_call_before_transition_callback_if_requirements_do_not_match
1456
+ called = false
1457
+
1458
+ observer = new_observer(@resource) do
1459
+ before_transition :from => :idling do
1460
+ called = true
1461
+ end
1462
+ end
1463
+
1464
+ @transition.perform
1465
+ assert !called
1466
+ end
1467
+
1468
+ def test_should_pass_transition_to_before_callbacks
1469
+ callback_args = nil
1470
+
1471
+ observer = new_observer(@resource) do
1472
+ before_transition do |*args|
1473
+ callback_args = args
1474
+ end
1475
+ end
1476
+
1477
+ @transition.perform
1478
+ assert_equal [@transition], callback_args
1479
+ end
1480
+
1481
+ def test_should_call_after_transition_callback_if_requirements_match
1482
+ called = false
1483
+
1484
+ observer = new_observer(@resource) do
1485
+ after_transition :from => :parked do
1486
+ called = true
1487
+ end
1488
+ end
1489
+
1490
+ @transition.perform
1491
+ assert called
1492
+ end
1493
+
1494
+ def test_should_not_call_after_transition_callback_if_requirements_do_not_match
1495
+ called = false
1496
+
1497
+ observer = new_observer(@resource) do
1498
+ after_transition :from => :idling do
1499
+ called = true
1500
+ end
1501
+ end
1502
+
1503
+ @transition.perform
1504
+ assert !called
1505
+ end
1506
+
1507
+ def test_should_pass_transition_to_after_callbacks
1508
+ callback_args = nil
1509
+
1510
+ observer = new_observer(@resource) do
1511
+ after_transition do |*args|
1512
+ callback_args = args
1513
+ end
1514
+ end
1515
+
1516
+ @transition.perform
1517
+ assert_equal [@transition], callback_args
1518
+ end
1519
+
1520
+ def test_should_call_around_transition_callback_if_requirements_match
1521
+ called = false
1522
+
1523
+ observer = new_observer(@resource) do
1524
+ around_transition :from => :parked do |block|
1525
+ called = true
1526
+ block.call
1527
+ end
1528
+ end
1529
+
1530
+ @transition.perform
1531
+ assert called
1532
+ end
1533
+
1534
+ def test_should_not_call_after_transition_callback_if_requirements_do_not_match
1535
+ called = false
1536
+
1537
+ observer = new_observer(@resource) do
1538
+ around_transition :from => :idling do |block|
1539
+ called = true
1540
+ block.call
1541
+ end
1542
+ end
1543
+
1544
+ @transition.perform
1545
+ assert !called
1546
+ end
1547
+
1548
+ def test_should_pass_transition_to_after_callbacks
1549
+ callback_args = nil
1550
+
1551
+ observer = new_observer(@resource) do
1552
+ around_transition do |*args|
1553
+ block = args.pop
1554
+ callback_args = args
1555
+ block.call
1556
+ end
1557
+ end
1558
+
1559
+ @transition.perform
1560
+ assert_equal [@transition], callback_args
1561
+ end
1562
+
1563
+ def test_should_raise_exception_if_targeting_invalid_machine
1564
+ assert_raise(RUBY_VERSION < '1.9' ? IndexError : KeyError) do
1565
+ new_observer(@resource) do
1566
+ before_transition :invalid, :from => :parked do
1567
+ end
1568
+ end
1569
+ end
1570
+ end
1571
+
1572
+ def test_should_allow_targeting_specific_machine
1573
+ @second_machine = StateMachine::Machine.new(@resource, :status)
1574
+ @resource.auto_migrate!
1575
+
1576
+ called_state = false
1577
+ called_status = false
1578
+
1579
+ observer = new_observer(@resource) do
1580
+ before_transition :state, :from => :parked do
1581
+ called_state = true
1582
+ end
1583
+
1584
+ before_transition :status, :from => :parked do
1585
+ called_status = true
1586
+ end
1587
+ end
1588
+
1589
+ @transition.perform
1590
+
1591
+ assert called_state
1592
+ assert !called_status
1593
+ end
1594
+
1595
+ def test_should_allow_targeting_multiple_specific_machines
1596
+ @second_machine = StateMachine::Machine.new(@resource, :status)
1597
+ @second_machine.state :parked, :idling
1598
+ @second_machine.event :ignite
1599
+ @resource.auto_migrate!
1600
+
1601
+ called_attribute = nil
1602
+
1603
+ attributes = []
1604
+ observer = new_observer(@resource) do
1605
+ before_transition :state, :status, :from => :parked do |transition|
1606
+ called_attribute = transition.attribute
1607
+ end
1608
+ end
1609
+
1610
+ @transition.perform
1611
+ assert_equal :state, called_attribute
1612
+
1613
+ StateMachine::Transition.new(@record, @second_machine, :ignite, :parked, :idling).perform
1614
+ assert_equal :status, called_attribute
1615
+ end
1616
+ end
1617
+
1618
+ class MachineWithMixedCallbacksTest < BaseTestCase
1619
+ def setup
1620
+ @resource = new_resource
1621
+ @machine = StateMachine::Machine.new(@resource)
1622
+ @machine.state :parked, :idling
1623
+ @machine.event :ignite
1624
+ @record = @resource.new(:state => 'parked')
1625
+ @transition = StateMachine::Transition.new(@record, @machine, :ignite, :parked, :idling)
1626
+
1627
+ @notifications = notifications = []
1628
+
1629
+ # Create callbacks
1630
+ @machine.before_transition {notifications << :callback_before_transition}
1631
+ @machine.after_transition {notifications << :callback_after_transition}
1632
+ @machine.around_transition do |block|
1633
+ notifications << :callback_around_before_transition
1634
+ block.call
1635
+ notifications << :callback_around_after_transition
1636
+ end
1637
+
1638
+ observer = new_observer(@resource) do
1639
+ before_transition do
1640
+ notifications << :observer_before_transition
1641
+ end
1642
+
1643
+ after_transition do
1644
+ notifications << :observer_after_transition
1645
+ end
1646
+
1647
+ around_transition do |block|
1648
+ notifications << :observer_around_before_transition
1649
+ block.call
1650
+ notifications << :observer_around_after_transition
1651
+ end
1652
+ end
1653
+
1654
+ @transition.perform
1655
+ end
1656
+
1657
+ def test_should_invoke_callbacks_in_specific_order
1658
+ expected = [
1659
+ :callback_before_transition,
1660
+ :callback_around_before_transition,
1661
+ :observer_before_transition,
1662
+ :observer_around_before_transition,
1663
+ :observer_around_after_transition,
1664
+ :callback_around_after_transition,
1665
+ :callback_after_transition,
1666
+ :observer_after_transition
1667
+ ]
1668
+
1669
+ assert_equal expected, @notifications
1670
+ end
1671
+ end
1672
+ rescue LoadError
1673
+ $stderr.puts "Skipping DataMapper Observer tests. `gem install dm-observer#{" -v #{ENV['VERSION']}" if ENV['VERSION']}` and try again."
1674
+ end
1675
+
1676
+ class MachineWithScopesTest < BaseTestCase
1677
+ def setup
1678
+ @resource = new_resource
1679
+ @machine = StateMachine::Machine.new(@resource)
1680
+ @machine.state :parked, :first_gear
1681
+ @machine.state :idling, :value => lambda {'idling'}
1682
+ end
1683
+
1684
+ def test_should_create_singular_with_scope
1685
+ assert @resource.respond_to?(:with_state)
1686
+ end
1687
+
1688
+ def test_should_only_include_records_with_state_in_singular_with_scope
1689
+ parked = @resource.create :state => 'parked'
1690
+ idling = @resource.create :state => 'idling'
1691
+
1692
+ assert_equal [parked], @resource.with_state(:parked)
1693
+ end
1694
+
1695
+ def test_should_create_plural_with_scope
1696
+ assert @resource.respond_to?(:with_states)
1697
+ end
1698
+
1699
+ def test_should_only_include_records_with_states_in_plural_with_scope
1700
+ parked = @resource.create :state => 'parked'
1701
+ idling = @resource.create :state => 'idling'
1702
+
1703
+ assert_equal [parked, idling], @resource.with_states(:parked, :idling)
1704
+ end
1705
+
1706
+ def test_should_create_singular_without_scope
1707
+ assert @resource.respond_to?(:without_state)
1708
+ end
1709
+
1710
+ def test_should_only_include_records_without_state_in_singular_without_scope
1711
+ parked = @resource.create :state => 'parked'
1712
+ idling = @resource.create :state => 'idling'
1713
+
1714
+ assert_equal [parked], @resource.without_state(:idling)
1715
+ end
1716
+
1717
+ def test_should_create_plural_without_scope
1718
+ assert @resource.respond_to?(:without_states)
1719
+ end
1720
+
1721
+ def test_should_only_include_records_without_states_in_plural_without_scope
1722
+ parked = @resource.create :state => 'parked'
1723
+ idling = @resource.create :state => 'idling'
1724
+ first_gear = @resource.create :state => 'first_gear'
1725
+
1726
+ assert_equal [parked, idling], @resource.without_states(:first_gear)
1727
+ end
1728
+
1729
+ def test_should_allow_chaining_scopes
1730
+ parked = @resource.create :state => 'parked'
1731
+ idling = @resource.create :state => 'idling'
1732
+
1733
+ assert_equal [idling], @resource.without_state(:parked).with_state(:idling)
1734
+ end
1735
+ end
1736
+
1737
+ class MachineWithScopesAndOwnerSubclassTest < BaseTestCase
1738
+ def setup
1739
+ @resource = new_resource
1740
+ @machine = StateMachine::Machine.new(@resource, :state)
1741
+
1742
+ @subclass = Class.new(@resource)
1743
+ @subclass_machine = @subclass.state_machine(:state) {}
1744
+ @subclass_machine.state :parked, :idling, :first_gear
1745
+ end
1746
+
1747
+ def test_should_only_include_records_with_subclass_states_in_with_scope
1748
+ parked = @subclass.create :state => 'parked'
1749
+ idling = @subclass.create :state => 'idling'
1750
+
1751
+ assert_equal [parked, idling], @subclass.with_states(:parked, :idling)
1752
+ end
1753
+
1754
+ def test_should_only_include_records_without_subclass_states_in_without_scope
1755
+ parked = @subclass.create :state => 'parked'
1756
+ idling = @subclass.create :state => 'idling'
1757
+ first_gear = @subclass.create :state => 'first_gear'
1758
+
1759
+ assert_equal [parked, idling], @subclass.without_states(:first_gear)
1760
+ end
1761
+ end
1762
+
1763
+ class MachineWithComplexPluralizationScopesTest < BaseTestCase
1764
+ def setup
1765
+ @resource = new_resource
1766
+ @machine = StateMachine::Machine.new(@resource, :status)
1767
+ end
1768
+
1769
+ def test_should_create_singular_with_scope
1770
+ assert @resource.respond_to?(:with_status)
1771
+ end
1772
+
1773
+ def test_should_create_plural_with_scope
1774
+ assert @resource.respond_to?(:with_statuses)
1775
+ end
1776
+ end
1777
+
1778
+ class MachineWithScopesAndJoinsTest < BaseTestCase
1779
+ def setup
1780
+ @company = new_resource(:company)
1781
+ DataMapperTest.const_set('Company', @company)
1782
+
1783
+ @vehicle = new_resource(:vehicle) do
1784
+ property :company_id, Integer
1785
+ auto_migrate!
1786
+
1787
+ belongs_to :company
1788
+ end
1789
+ DataMapperTest.const_set('Vehicle', @vehicle)
1790
+
1791
+ @company_machine = StateMachine::Machine.new(@company, :initial => :active)
1792
+ @vehicle_machine = StateMachine::Machine.new(@vehicle, :initial => :parked)
1793
+ @vehicle_machine.state :idling
1794
+
1795
+ @ford = @company.create
1796
+ @mustang = @vehicle.create(:company => @ford)
1797
+ end
1798
+
1799
+ def test_should_find_records_in_with_scope
1800
+ assert_equal [@mustang], @vehicle.with_states(:parked).all(Vehicle.company.state => 'active')
1801
+ end
1802
+
1803
+ def test_should_find_records_in_without_scope
1804
+ assert_equal [@mustang], @vehicle.without_states(:idling).all(Vehicle.company.state => 'active')
1805
+ end
1806
+
1807
+ def teardown
1808
+ DataMapperTest.class_eval do
1809
+ remove_const('Vehicle')
1810
+ remove_const('Company')
1811
+ end
1812
+ end
1813
+ end
1814
+ end