joelind-state_machine 0.8.1

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 (78) hide show
  1. data/CHANGELOG.rdoc +297 -0
  2. data/LICENSE +20 -0
  3. data/README.rdoc +466 -0
  4. data/Rakefile +98 -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.rb +388 -0
  28. data/lib/state_machine/assertions.rb +36 -0
  29. data/lib/state_machine/callback.rb +189 -0
  30. data/lib/state_machine/condition_proxy.rb +94 -0
  31. data/lib/state_machine/eval_helpers.rb +67 -0
  32. data/lib/state_machine/event.rb +252 -0
  33. data/lib/state_machine/event_collection.rb +122 -0
  34. data/lib/state_machine/extensions.rb +149 -0
  35. data/lib/state_machine/guard.rb +230 -0
  36. data/lib/state_machine/integrations.rb +68 -0
  37. data/lib/state_machine/integrations/active_record.rb +492 -0
  38. data/lib/state_machine/integrations/active_record/locale.rb +11 -0
  39. data/lib/state_machine/integrations/active_record/observer.rb +41 -0
  40. data/lib/state_machine/integrations/data_mapper.rb +351 -0
  41. data/lib/state_machine/integrations/data_mapper/observer.rb +139 -0
  42. data/lib/state_machine/integrations/sequel.rb +322 -0
  43. data/lib/state_machine/machine.rb +1467 -0
  44. data/lib/state_machine/machine_collection.rb +155 -0
  45. data/lib/state_machine/matcher.rb +123 -0
  46. data/lib/state_machine/matcher_helpers.rb +54 -0
  47. data/lib/state_machine/node_collection.rb +152 -0
  48. data/lib/state_machine/state.rb +249 -0
  49. data/lib/state_machine/state_collection.rb +112 -0
  50. data/lib/state_machine/transition.rb +394 -0
  51. data/tasks/state_machine.rake +1 -0
  52. data/tasks/state_machine.rb +30 -0
  53. data/test/classes/switch.rb +11 -0
  54. data/test/functional/state_machine_test.rb +941 -0
  55. data/test/test_helper.rb +4 -0
  56. data/test/unit/assertions_test.rb +40 -0
  57. data/test/unit/callback_test.rb +455 -0
  58. data/test/unit/condition_proxy_test.rb +328 -0
  59. data/test/unit/eval_helpers_test.rb +120 -0
  60. data/test/unit/event_collection_test.rb +326 -0
  61. data/test/unit/event_test.rb +743 -0
  62. data/test/unit/guard_test.rb +908 -0
  63. data/test/unit/integrations/active_record_test.rb +1374 -0
  64. data/test/unit/integrations/data_mapper_test.rb +962 -0
  65. data/test/unit/integrations/sequel_test.rb +859 -0
  66. data/test/unit/integrations_test.rb +42 -0
  67. data/test/unit/invalid_event_test.rb +7 -0
  68. data/test/unit/invalid_transition_test.rb +7 -0
  69. data/test/unit/machine_collection_test.rb +938 -0
  70. data/test/unit/machine_test.rb +2004 -0
  71. data/test/unit/matcher_helpers_test.rb +37 -0
  72. data/test/unit/matcher_test.rb +155 -0
  73. data/test/unit/node_collection_test.rb +207 -0
  74. data/test/unit/state_collection_test.rb +280 -0
  75. data/test/unit/state_machine_test.rb +31 -0
  76. data/test/unit/state_test.rb +795 -0
  77. data/test/unit/transition_test.rb +1212 -0
  78. metadata +163 -0
@@ -0,0 +1,859 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../../test_helper')
2
+
3
+ begin
4
+ # Load library
5
+ require 'rubygems'
6
+
7
+ gem 'sequel', ENV['SEQUEL_VERSION'] ? "=#{ENV['SEQUEL_VERSION']}" : '>=2.8.0'
8
+ require 'sequel'
9
+ require 'logger'
10
+
11
+ # Establish database connection
12
+ DB = Sequel.connect('sqlite:///', :loggers => [Logger.new("#{File.dirname(__FILE__)}/../../sequel.log")])
13
+
14
+ module SequelTest
15
+ class BaseTestCase < Test::Unit::TestCase
16
+ def default_test
17
+ end
18
+
19
+ protected
20
+ # Creates a new Sequel model (and the associated table)
21
+ def new_model(auto_migrate = true, &block)
22
+ DB.create_table! :foo do
23
+ primary_key :id
24
+ column :state, :string
25
+ end if auto_migrate
26
+ model = Class.new(Sequel::Model(:foo)) do
27
+ self.raise_on_save_failure = false
28
+ def self.name; 'SequelTest::Foo'; end
29
+ end
30
+ model.plugin(:validation_class_methods) if model.respond_to?(:plugin)
31
+ model.plugin(:hook_class_methods) if model.respond_to?(:plugin)
32
+ model.class_eval(&block) if block_given?
33
+ model
34
+ end
35
+ end
36
+
37
+ class IntegrationTest < BaseTestCase
38
+ def test_should_match_if_class_inherits_from_sequel
39
+ assert StateMachine::Integrations::Sequel.matches?(new_model)
40
+ end
41
+
42
+ def test_should_not_match_if_class_does_not_inherit_from_sequel
43
+ assert !StateMachine::Integrations::Sequel.matches?(Class.new)
44
+ end
45
+ end
46
+
47
+ class MachineByDefaultTest < BaseTestCase
48
+ def setup
49
+ @model = new_model
50
+ @machine = StateMachine::Machine.new(@model)
51
+ end
52
+
53
+ def test_should_use_save_as_action
54
+ assert_equal :save, @machine.action
55
+ end
56
+
57
+ def test_should_use_transactions
58
+ assert_equal true, @machine.use_transactions
59
+ end
60
+ end
61
+
62
+ class MachineTest < BaseTestCase
63
+ def setup
64
+ @model = new_model
65
+ @machine = StateMachine::Machine.new(@model)
66
+ @machine.state :parked, :first_gear
67
+ @machine.state :idling, :value => lambda {'idling'}
68
+ end
69
+
70
+ def test_should_create_singular_with_scope
71
+ assert @model.respond_to?(:with_state)
72
+ end
73
+
74
+ def test_should_only_include_records_with_state_in_singular_with_scope
75
+ parked = @model.create :state => 'parked'
76
+ idling = @model.create :state => 'idling'
77
+
78
+ assert_equal [parked], @model.with_state(:parked).all
79
+ end
80
+
81
+ def test_should_create_plural_with_scope
82
+ assert @model.respond_to?(:with_states)
83
+ end
84
+
85
+ def test_should_only_include_records_with_states_in_plural_with_scope
86
+ parked = @model.create :state => 'parked'
87
+ idling = @model.create :state => 'idling'
88
+
89
+ assert_equal [parked, idling], @model.with_states(:parked, :idling).all
90
+ end
91
+
92
+ def test_should_create_singular_without_scope
93
+ assert @model.respond_to?(:without_state)
94
+ end
95
+
96
+ def test_should_only_include_records_without_state_in_singular_without_scope
97
+ parked = @model.create :state => 'parked'
98
+ idling = @model.create :state => 'idling'
99
+
100
+ assert_equal [parked], @model.without_state(:idling).all
101
+ end
102
+
103
+ def test_should_create_plural_without_scope
104
+ assert @model.respond_to?(:without_states)
105
+ end
106
+
107
+ def test_should_only_include_records_without_states_in_plural_without_scope
108
+ parked = @model.create :state => 'parked'
109
+ idling = @model.create :state => 'idling'
110
+ first_gear = @model.create :state => 'first_gear'
111
+
112
+ assert_equal [parked, idling], @model.without_states(:first_gear).all
113
+ end
114
+
115
+ def test_should_allow_chaining_scopes_and_fitlers
116
+ parked = @model.create :state => 'parked'
117
+ idling = @model.create :state => 'idling'
118
+
119
+ assert_equal [idling], @model.without_state(:parked).filter(:state => 'idling').all
120
+ end
121
+
122
+ def test_should_rollback_transaction_if_false
123
+ @machine.within_transaction(@model.new) do
124
+ @model.create
125
+ false
126
+ end
127
+
128
+ assert_equal 0, @model.count
129
+ end
130
+
131
+ def test_should_not_rollback_transaction_if_true
132
+ @machine.within_transaction(@model.new) do
133
+ @model.create
134
+ true
135
+ end
136
+
137
+ assert_equal 1, @model.count
138
+ end
139
+
140
+ def test_should_invalidate_using_errors
141
+ record = @model.new
142
+ record.state = 'parked'
143
+
144
+ @machine.invalidate(record, :state, :invalid_transition, [[:event, :park]])
145
+
146
+ assert_equal ['cannot transition via "park"'], record.errors.on(:state)
147
+ end
148
+
149
+ def test_should_auto_prefix_custom_attributes_on_invalidation
150
+ record = @model.new
151
+ @machine.invalidate(record, :event, :invalid)
152
+
153
+ assert_equal ['is invalid'], record.errors.on(:state_event)
154
+ end
155
+
156
+ def test_should_clear_errors_on_reset
157
+ record = @model.new
158
+ record.state = 'parked'
159
+ record.errors.add(:state, 'is invalid')
160
+
161
+ @machine.reset(record)
162
+ assert_nil record.errors.on(:id)
163
+ end
164
+
165
+ def test_should_not_override_the_column_reader
166
+ record = @model.new
167
+ record[:state] = 'parked'
168
+ assert_equal 'parked', record.state
169
+ end
170
+
171
+ def test_should_not_override_the_column_writer
172
+ record = @model.new
173
+ record.state = 'parked'
174
+ assert_equal 'parked', record[:state]
175
+ end
176
+ end
177
+
178
+ class MachineUnmigratedTest < BaseTestCase
179
+ def setup
180
+ @model = new_model(false)
181
+ end
182
+
183
+ def test_should_allow_machine_creation
184
+ assert_nothing_raised { StateMachine::Machine.new(@model) }
185
+ end
186
+ end
187
+
188
+ class MachineWithStaticInitialStateTest < BaseTestCase
189
+ def setup
190
+ @model = new_model do
191
+ attr_accessor :value
192
+ end
193
+ @machine = StateMachine::Machine.new(@model, :initial => :parked)
194
+ end
195
+
196
+ def test_should_set_initial_state_on_created_object
197
+ record = @model.new
198
+ assert_equal 'parked', record.state
199
+ end
200
+
201
+ def test_should_still_set_attributes
202
+ record = @model.new(:value => 1)
203
+ assert_equal 1, record.value
204
+ end
205
+
206
+ def test_should_still_allow_initialize_blocks
207
+ block_args = nil
208
+ record = @model.new do |*args|
209
+ block_args = args
210
+ end
211
+
212
+ assert_equal [record], block_args
213
+ end
214
+
215
+ def test_should_not_have_any_changed_columns
216
+ record = @model.new
217
+ assert record.changed_columns.empty?
218
+ end
219
+
220
+ def test_should_set_attributes_prior_to_after_initialize_hook
221
+ state = nil
222
+ @model.class_eval do
223
+ define_method(:after_initialize) do
224
+ state = self.state
225
+ end
226
+ end
227
+ @model.new
228
+ assert_equal 'parked', state
229
+ end
230
+
231
+ def test_should_set_initial_state_before_setting_attributes
232
+ @model.class_eval do
233
+ attr_accessor :state_during_setter
234
+
235
+ define_method(:value=) do |value|
236
+ self.state_during_setter = state
237
+ end
238
+ end
239
+
240
+ record = @model.new(:value => 1)
241
+ assert_equal 'parked', record.state_during_setter
242
+ end
243
+
244
+ def test_should_not_set_initial_state_after_already_initialized
245
+ record = @model.new(:value => 1)
246
+ assert_equal 'parked', record.state
247
+
248
+ record.state = 'idling'
249
+ record.set({})
250
+ assert_equal 'idling', record.state
251
+ end
252
+ end
253
+
254
+ class MachineWithDynamicInitialStateTest < BaseTestCase
255
+ def setup
256
+ @model = new_model do
257
+ attr_accessor :value
258
+ end
259
+ @machine = StateMachine::Machine.new(@model, :initial => lambda {|object| :parked})
260
+ @machine.state :parked
261
+ end
262
+
263
+ def test_should_set_initial_state_on_created_object
264
+ record = @model.new
265
+ assert_equal 'parked', record.state
266
+ end
267
+
268
+ def test_should_still_set_attributes
269
+ record = @model.new(:value => 1)
270
+ assert_equal 1, record.value
271
+ end
272
+
273
+ def test_should_still_allow_initialize_blocks
274
+ block_args = nil
275
+ record = @model.new do |*args|
276
+ block_args = args
277
+ end
278
+
279
+ assert_equal [record], block_args
280
+ end
281
+
282
+ def test_should_not_have_any_changed_columns
283
+ record = @model.new
284
+ assert record.changed_columns.empty?
285
+ end
286
+
287
+ def test_should_set_attributes_prior_to_after_initialize_hook
288
+ state = nil
289
+ @model.class_eval do
290
+ define_method(:after_initialize) do
291
+ state = self.state
292
+ end
293
+ end
294
+ @model.new
295
+ assert_equal 'parked', state
296
+ end
297
+
298
+ def test_should_set_initial_state_after_setting_attributes
299
+ @model.class_eval do
300
+ attr_accessor :state_during_setter
301
+
302
+ define_method(:value=) do |value|
303
+ self.state_during_setter = state || 'nil'
304
+ end
305
+ end
306
+
307
+ record = @model.new(:value => 1)
308
+ assert_equal 'nil', record.state_during_setter
309
+ end
310
+
311
+ def test_should_not_set_initial_state_after_already_initialized
312
+ record = @model.new(:value => 1)
313
+ assert_equal 'parked', record.state
314
+
315
+ record.state = 'idling'
316
+ record.set({})
317
+ assert_equal 'idling', record.state
318
+ end
319
+ end
320
+
321
+ class MachineWithColumnDefaultTest < BaseTestCase
322
+ def setup
323
+ @model = new_model
324
+ DB.alter_table :foo do
325
+ add_column :status, :string, :default => 'idling'
326
+ end
327
+ @model.class_eval { get_db_schema(true) }
328
+
329
+ @machine = StateMachine::Machine.new(@model, :status, :initial => :parked)
330
+ @record = @model.new
331
+ end
332
+
333
+ def test_should_use_machine_default
334
+ assert_equal 'parked', @record.status
335
+ end
336
+ end
337
+
338
+ class MachineWithNonColumnStateAttributeUndefinedTest < BaseTestCase
339
+ def setup
340
+ @model = new_model do
341
+ def initialize
342
+ # Skip attribute initialization
343
+ @initialized_state_machines = true
344
+ super
345
+ end
346
+ end
347
+
348
+ @machine = StateMachine::Machine.new(@model, :status, :initial => 'parked')
349
+ @record = @model.new
350
+ end
351
+
352
+ def test_should_not_define_a_reader_attribute_for_the_attribute
353
+ assert !@record.respond_to?(:status)
354
+ end
355
+
356
+ def test_should_not_define_a_writer_attribute_for_the_attribute
357
+ assert !@record.respond_to?(:status=)
358
+ end
359
+
360
+ def test_should_define_an_attribute_predicate
361
+ assert @record.respond_to?(:status?)
362
+ end
363
+ end
364
+
365
+ class MachineWithNonColumnStateAttributeDefinedTest < BaseTestCase
366
+ def setup
367
+ @model = new_model do
368
+ attr_accessor :status
369
+ end
370
+
371
+ @machine = StateMachine::Machine.new(@model, :status, :initial => 'parked')
372
+ @record = @model.new
373
+ end
374
+
375
+ def test_should_set_initial_state_on_created_object
376
+ assert_equal 'parked', @record.status
377
+ end
378
+ end
379
+
380
+ class MachineWithComplexPluralizationTest < BaseTestCase
381
+ def setup
382
+ @model = new_model
383
+ @machine = StateMachine::Machine.new(@model, :status)
384
+ end
385
+
386
+ def test_should_create_singular_with_scope
387
+ assert @model.respond_to?(:with_status)
388
+ end
389
+
390
+ def test_should_create_plural_with_scope
391
+ assert @model.respond_to?(:with_statuses)
392
+ end
393
+ end
394
+
395
+ class MachineWithOwnerSubclassTest < BaseTestCase
396
+ def setup
397
+ @model = new_model
398
+ @machine = StateMachine::Machine.new(@model, :state)
399
+
400
+ @subclass = Class.new(@model)
401
+ @subclass_machine = @subclass.state_machine(:state) {}
402
+ @subclass_machine.state :parked, :idling, :first_gear
403
+ end
404
+
405
+ def test_should_only_include_records_with_subclass_states_in_with_scope
406
+ parked = @subclass.create :state => 'parked'
407
+ idling = @subclass.create :state => 'idling'
408
+
409
+ assert_equal [parked, idling], @subclass.with_states(:parked, :idling).all
410
+ end
411
+
412
+ def test_should_only_include_records_without_subclass_states_in_without_scope
413
+ parked = @subclass.create :state => 'parked'
414
+ idling = @subclass.create :state => 'idling'
415
+ first_gear = @subclass.create :state => 'first_gear'
416
+
417
+ assert_equal [parked, idling], @subclass.without_states(:first_gear).all
418
+ end
419
+ end
420
+
421
+ class MachineWithCustomAttributeTest < BaseTestCase
422
+ def setup
423
+ @model = new_model do
424
+ alias_method :vehicle_status, :state
425
+ alias_method :vehicle_status=, :state=
426
+ end
427
+
428
+ @machine = StateMachine::Machine.new(@model, :status, :attribute => :vehicle_status)
429
+ @machine.state :parked
430
+
431
+ @record = @model.new
432
+ end
433
+
434
+ def test_should_add_validation_errors_to_custom_attribute
435
+ @record.vehicle_status = 'invalid'
436
+
437
+ assert !@record.valid?
438
+ assert_equal ['is invalid'], @record.errors.on(:vehicle_status)
439
+
440
+ @record.vehicle_status = 'parked'
441
+ assert @record.valid?
442
+ end
443
+ end
444
+
445
+ class MachineWithInitializedStateTest < BaseTestCase
446
+ def setup
447
+ @model = new_model
448
+ @machine = StateMachine::Machine.new(@model, :initial => :parked)
449
+ @machine.state nil, :idling
450
+ end
451
+
452
+ def test_should_allow_nil_initial_state_when_static
453
+ record = @model.new(:state => nil)
454
+ assert_nil record.state
455
+ end
456
+
457
+ def test_should_allow_nil_initial_state_when_dynamic
458
+ @machine.initial_state = lambda {:parked}
459
+ record = @model.new(:state => nil)
460
+ assert_nil record.state
461
+ end
462
+
463
+ def test_should_allow_different_initial_state_when_static
464
+ record = @model.new(:state => 'idling')
465
+ assert_equal 'idling', record.state
466
+ end
467
+
468
+ def test_should_allow_different_initial_state_when_dynamic
469
+ @machine.initial_state = lambda {:parked}
470
+ record = @model.new(:state => 'idling')
471
+ assert_equal 'idling', record.state
472
+ end
473
+
474
+ def test_should_use_default_state_if_protected
475
+ @model.class_eval do
476
+ self.strict_param_setting = false
477
+ set_restricted_columns :state
478
+ end
479
+
480
+ record = @model.new(:state => 'idling')
481
+ assert_equal 'parked', record.state
482
+ end
483
+ end
484
+
485
+ class MachineWithCallbacksTest < BaseTestCase
486
+ def setup
487
+ @model = new_model
488
+ @machine = StateMachine::Machine.new(@model)
489
+ @machine.state :parked, :idling
490
+ @machine.event :ignite
491
+ @record = @model.new(:state => 'parked')
492
+ @transition = StateMachine::Transition.new(@record, @machine, :ignite, :parked, :idling)
493
+ end
494
+
495
+ def test_should_run_before_callbacks
496
+ called = false
497
+ @machine.before_transition(lambda {called = true})
498
+
499
+ @transition.perform
500
+ assert called
501
+ end
502
+
503
+ def test_should_pass_transition_to_before_callbacks_with_one_argument
504
+ transition = nil
505
+ @machine.before_transition(lambda {|arg| transition = arg})
506
+
507
+ @transition.perform
508
+ assert_equal @transition, transition
509
+ end
510
+
511
+ def test_should_pass_transition_to_before_callbacks_with_multiple_arguments
512
+ callback_args = nil
513
+ @machine.before_transition(lambda {|*args| callback_args = args})
514
+
515
+ @transition.perform
516
+ assert_equal [@transition], callback_args
517
+ end
518
+
519
+ def test_should_run_before_callbacks_within_the_context_of_the_record
520
+ context = nil
521
+ @machine.before_transition(lambda {context = self})
522
+
523
+ @transition.perform
524
+ assert_equal @record, context
525
+ end
526
+
527
+ def test_should_run_after_callbacks
528
+ called = false
529
+ @machine.after_transition(lambda {called = true})
530
+
531
+ @transition.perform
532
+ assert called
533
+ end
534
+
535
+ def test_should_pass_transition_to_after_callbacks_with_multiple_arguments
536
+ callback_args = nil
537
+ @machine.after_transition(lambda {|*args| callback_args = args})
538
+
539
+ @transition.perform
540
+ assert_equal [@transition], callback_args
541
+ end
542
+
543
+ def test_should_run_after_callbacks_with_the_context_of_the_record
544
+ context = nil
545
+ @machine.after_transition(lambda {context = self})
546
+
547
+ @transition.perform
548
+ assert_equal @record, context
549
+ end
550
+
551
+ def test_should_allow_symbolic_callbacks
552
+ callback_args = nil
553
+
554
+ klass = class << @record; self; end
555
+ klass.send(:define_method, :after_ignite) do |*args|
556
+ callback_args = args
557
+ end
558
+
559
+ @machine.before_transition(:after_ignite)
560
+
561
+ @transition.perform
562
+ assert_equal [@transition], callback_args
563
+ end
564
+
565
+ def test_should_allow_string_callbacks
566
+ class << @record
567
+ attr_reader :callback_result
568
+ end
569
+
570
+ @machine.before_transition('@callback_result = [1, 2, 3]')
571
+ @transition.perform
572
+
573
+ assert_equal [1, 2, 3], @record.callback_result
574
+ end
575
+ end
576
+
577
+ class MachineWithLoopbackTest < BaseTestCase
578
+ def setup
579
+ changed_columns = nil
580
+
581
+ @model = new_model do
582
+ # Simulate timestamps plugin
583
+ define_method(:before_update) do
584
+ changed_columns = self.changed_columns.dup
585
+
586
+ super()
587
+ self.updated_at = Time.now if changed_columns.any?
588
+ end
589
+ end
590
+
591
+ DB.alter_table :foo do
592
+ add_column :updated_at, :datetime
593
+ end
594
+ @model.class_eval { get_db_schema(true) }
595
+
596
+ @machine = StateMachine::Machine.new(@model, :initial => :parked)
597
+ @machine.event :park
598
+
599
+ @record = @model.create(:updated_at => Time.now - 1)
600
+ @timestamp = @record.updated_at
601
+
602
+ @transition = StateMachine::Transition.new(@record, @machine, :park, :parked, :parked)
603
+ @transition.perform
604
+
605
+ @changed_columns = changed_columns
606
+ end
607
+
608
+ def test_should_include_state_in_changed_columns
609
+ assert_equal [:state], @changed_columns
610
+ end
611
+
612
+ def test_should_update_record
613
+ assert_not_equal @timestamp, @record.updated_at
614
+ end
615
+ end
616
+
617
+ class MachineWithValidationsTest < BaseTestCase
618
+ def setup
619
+ @model = new_model
620
+ @machine = StateMachine::Machine.new(@model)
621
+ @machine.state :parked
622
+
623
+ @record = @model.new
624
+ end
625
+
626
+ def test_should_be_valid_if_state_is_known
627
+ @record.state = 'parked'
628
+
629
+ assert @record.valid?
630
+ end
631
+
632
+ def test_should_not_be_valid_if_state_is_unknown
633
+ @record.state = 'invalid'
634
+
635
+ assert !@record.valid?
636
+ assert_equal ['state is invalid'], @record.errors.full_messages
637
+ end
638
+ end
639
+
640
+ class MachineWithStateDrivenValidationsTest < BaseTestCase
641
+ def setup
642
+ @model = new_model do
643
+ attr_accessor :seatbelt
644
+ end
645
+
646
+ @machine = StateMachine::Machine.new(@model)
647
+ @machine.state :first_gear do
648
+ validates_presence_of :seatbelt
649
+ end
650
+ @machine.other_states :parked
651
+ end
652
+
653
+ def test_should_be_valid_if_validation_fails_outside_state_scope
654
+ record = @model.new(:state => 'parked', :seatbelt => nil)
655
+ assert record.valid?
656
+ end
657
+
658
+ def test_should_be_invalid_if_validation_fails_within_state_scope
659
+ record = @model.new(:state => 'first_gear', :seatbelt => nil)
660
+ assert !record.valid?
661
+ end
662
+
663
+ def test_should_be_valid_if_validation_succeeds_within_state_scope
664
+ record = @model.new(:state => 'first_gear', :seatbelt => true)
665
+ assert record.valid?
666
+ end
667
+ end
668
+
669
+ class MachineWithEventAttributesOnValidationTest < BaseTestCase
670
+ def setup
671
+ @model = new_model
672
+ @machine = StateMachine::Machine.new(@model)
673
+ @machine.event :ignite do
674
+ transition :parked => :idling
675
+ end
676
+
677
+ @record = @model.new
678
+ @record.state = 'parked'
679
+ @record.state_event = 'ignite'
680
+ end
681
+
682
+ def test_should_fail_if_event_is_invalid
683
+ @record.state_event = 'invalid'
684
+ assert !@record.valid?
685
+ assert_equal ['state_event is invalid'], @record.errors.full_messages
686
+ end
687
+
688
+ def test_should_fail_if_event_has_no_transition
689
+ @record.state = 'idling'
690
+ assert !@record.valid?
691
+ assert_equal ['state_event cannot transition when idling'], @record.errors.full_messages
692
+ end
693
+
694
+ def test_should_be_successful_if_event_has_transition
695
+ assert @record.valid?
696
+ end
697
+
698
+ def test_should_run_before_callbacks
699
+ ran_callback = false
700
+ @machine.before_transition { ran_callback = true }
701
+
702
+ @record.valid?
703
+ assert ran_callback
704
+ end
705
+
706
+ def test_should_persist_new_state
707
+ @record.valid?
708
+ assert_equal 'idling', @record.state
709
+ end
710
+
711
+ def test_should_not_run_after_callbacks
712
+ ran_callback = false
713
+ @machine.after_transition { ran_callback = true }
714
+
715
+ @record.valid?
716
+ assert !ran_callback
717
+ end
718
+
719
+ def test_should_not_run_after_callbacks_with_failures_disabled_if_validation_fails
720
+ @model.class_eval do
721
+ attr_accessor :seatbelt
722
+ validates_presence_of :seatbelt
723
+ end
724
+
725
+ ran_callback = false
726
+ @machine.after_transition { ran_callback = true }
727
+
728
+ @record.valid?
729
+ assert !ran_callback
730
+ end
731
+
732
+ def test_should_run_after_callbacks_with_failures_enabled_if_validation_fails
733
+ @model.class_eval do
734
+ attr_accessor :seatbelt
735
+ validates_presence_of :seatbelt
736
+ end
737
+
738
+ ran_callback = false
739
+ @machine.after_transition(:include_failures => true) { ran_callback = true }
740
+
741
+ @record.valid?
742
+ assert ran_callback
743
+ end
744
+ end
745
+
746
+ class MachineWithEventAttributesOnSaveTest < BaseTestCase
747
+ def setup
748
+ @model = new_model
749
+ @machine = StateMachine::Machine.new(@model)
750
+ @machine.event :ignite do
751
+ transition :parked => :idling
752
+ end
753
+
754
+ @record = @model.new
755
+ @record.state = 'parked'
756
+ @record.state_event = 'ignite'
757
+ end
758
+
759
+ def test_should_fail_if_event_is_invalid
760
+ @record.state_event = 'invalid'
761
+ assert !@record.save
762
+ end
763
+
764
+ def test_should_fail_if_event_has_no_transition
765
+ @record.state = 'idling'
766
+ assert !@record.save
767
+ end
768
+
769
+ def test_should_be_successful_if_event_has_transition
770
+ assert @record.save
771
+ end
772
+
773
+ def test_should_run_before_callbacks
774
+ ran_callback = false
775
+ @machine.before_transition { ran_callback = true }
776
+
777
+ @record.save
778
+ assert ran_callback
779
+ end
780
+
781
+ def test_should_run_before_callbacks_once
782
+ before_count = 0
783
+ @machine.before_transition { before_count += 1 }
784
+
785
+ @record.save
786
+ assert_equal 1, before_count
787
+ end
788
+
789
+ def test_should_persist_new_state
790
+ @record.save
791
+ assert_equal 'idling', @record.state
792
+ end
793
+
794
+ def test_should_run_after_callbacks
795
+ ran_callback = false
796
+ @machine.after_transition { ran_callback = true }
797
+
798
+ @record.save
799
+ assert ran_callback
800
+ end
801
+
802
+ def test_should_not_run_after_callbacks_with_failures_disabled_if_fails
803
+ @model.before_create {|record| false}
804
+
805
+ ran_callback = false
806
+ @machine.after_transition { ran_callback = true }
807
+
808
+ @record.save
809
+ assert !ran_callback
810
+ end
811
+
812
+ def test_should_run_after_callbacks_with_failures_enabled_if_fails
813
+ @model.before_create {|record| false}
814
+
815
+ ran_callback = false
816
+ @machine.after_transition(:include_failures => true) { ran_callback = true }
817
+
818
+ @record.save
819
+ assert ran_callback
820
+ end
821
+ end
822
+
823
+ class MachineWithEventAttributesOnCustomActionTest < BaseTestCase
824
+ def setup
825
+ @superclass = new_model do
826
+ def persist
827
+ save
828
+ end
829
+ end
830
+ @model = Class.new(@superclass)
831
+ @machine = StateMachine::Machine.new(@model, :action => :persist)
832
+ @machine.event :ignite do
833
+ transition :parked => :idling
834
+ end
835
+
836
+ @record = @model.new
837
+ @record.state = 'parked'
838
+ @record.state_event = 'ignite'
839
+ end
840
+
841
+ def test_should_not_transition_on_valid?
842
+ @record.valid?
843
+ assert_equal 'parked', @record.state
844
+ end
845
+
846
+ def test_should_not_transition_on_save
847
+ @record.save
848
+ assert_equal 'parked', @record.state
849
+ end
850
+
851
+ def test_should_transition_on_custom_action
852
+ @record.persist
853
+ assert_equal 'idling', @record.state
854
+ end
855
+ end
856
+ end
857
+ rescue LoadError
858
+ $stderr.puts "Skipping Sequel tests. `gem install sequel#{" -v #{ENV['SEQUEL_VERSION']}" if ENV['SEQUEL_VERSION']}` and try again."
859
+ end