state_machine 0.6.3 → 0.7.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 (56) hide show
  1. data/CHANGELOG.rdoc +31 -1
  2. data/README.rdoc +33 -21
  3. data/Rakefile +2 -2
  4. data/examples/merb-rest/controller.rb +51 -0
  5. data/examples/merb-rest/model.rb +28 -0
  6. data/examples/merb-rest/view_edit.html.erb +24 -0
  7. data/examples/merb-rest/view_index.html.erb +23 -0
  8. data/examples/merb-rest/view_new.html.erb +13 -0
  9. data/examples/merb-rest/view_show.html.erb +17 -0
  10. data/examples/rails-rest/controller.rb +43 -0
  11. data/examples/rails-rest/migration.rb +11 -0
  12. data/examples/rails-rest/model.rb +23 -0
  13. data/examples/rails-rest/view_edit.html.erb +25 -0
  14. data/examples/rails-rest/view_index.html.erb +23 -0
  15. data/examples/rails-rest/view_new.html.erb +14 -0
  16. data/examples/rails-rest/view_show.html.erb +17 -0
  17. data/lib/state_machine/assertions.rb +2 -2
  18. data/lib/state_machine/callback.rb +14 -8
  19. data/lib/state_machine/condition_proxy.rb +3 -3
  20. data/lib/state_machine/event.rb +19 -21
  21. data/lib/state_machine/event_collection.rb +114 -0
  22. data/lib/state_machine/extensions.rb +127 -11
  23. data/lib/state_machine/guard.rb +1 -1
  24. data/lib/state_machine/integrations/active_record/locale.rb +2 -1
  25. data/lib/state_machine/integrations/active_record.rb +117 -39
  26. data/lib/state_machine/integrations/data_mapper/observer.rb +20 -64
  27. data/lib/state_machine/integrations/data_mapper.rb +71 -26
  28. data/lib/state_machine/integrations/sequel.rb +69 -21
  29. data/lib/state_machine/machine.rb +267 -139
  30. data/lib/state_machine/machine_collection.rb +145 -0
  31. data/lib/state_machine/matcher.rb +2 -2
  32. data/lib/state_machine/node_collection.rb +9 -4
  33. data/lib/state_machine/state.rb +22 -32
  34. data/lib/state_machine/state_collection.rb +66 -17
  35. data/lib/state_machine/transition.rb +259 -28
  36. data/lib/state_machine.rb +121 -56
  37. data/tasks/state_machine.rake +1 -0
  38. data/tasks/state_machine.rb +26 -0
  39. data/test/active_record.log +116877 -0
  40. data/test/functional/state_machine_test.rb +118 -12
  41. data/test/sequel.log +28542 -0
  42. data/test/unit/callback_test.rb +46 -1
  43. data/test/unit/condition_proxy_test.rb +55 -28
  44. data/test/unit/event_collection_test.rb +228 -0
  45. data/test/unit/event_test.rb +51 -46
  46. data/test/unit/integrations/active_record_test.rb +128 -70
  47. data/test/unit/integrations/data_mapper_test.rb +150 -58
  48. data/test/unit/integrations/sequel_test.rb +63 -6
  49. data/test/unit/invalid_event_test.rb +7 -0
  50. data/test/unit/machine_collection_test.rb +678 -0
  51. data/test/unit/machine_test.rb +198 -91
  52. data/test/unit/node_collection_test.rb +33 -30
  53. data/test/unit/state_collection_test.rb +112 -5
  54. data/test/unit/state_test.rb +23 -3
  55. data/test/unit/transition_test.rb +750 -89
  56. metadata +28 -3
@@ -64,6 +64,10 @@ begin
64
64
  def test_should_use_save_as_action
65
65
  assert_equal :save, @machine.action
66
66
  end
67
+
68
+ def test_should_not_use_transactions
69
+ assert_equal false, @machine.use_transactions
70
+ end
67
71
  end
68
72
 
69
73
  class MachineTest < BaseTestCase
@@ -125,13 +129,13 @@ begin
125
129
  assert_equal [idling], @resource.without_state(:parked).with_state(:idling)
126
130
  end
127
131
 
128
- def test_should_rollback_transaction_if_false
132
+ def test_should_not_rollback_transaction_if_false
129
133
  @machine.within_transaction(@resource.new) do
130
134
  @resource.create
131
135
  false
132
136
  end
133
137
 
134
- assert_equal 0, @resource.all.size
138
+ assert_equal 1, @resource.all.size
135
139
  end
136
140
 
137
141
  def test_should_not_rollback_transaction_if_true
@@ -143,24 +147,6 @@ begin
143
147
  assert_equal 1, @resource.all.size
144
148
  end
145
149
 
146
- def test_should_invalidate_using_errors
147
- record = @resource.new
148
- record.state = 'parked'
149
-
150
- @machine.invalidate(record, StateMachine::Event.new(@machine, :park))
151
-
152
- assert_equal ['cannot be transitioned via :park from :parked'], record.errors.on(:state)
153
- end
154
-
155
- def test_should_clear_errors_on_reset
156
- record = @resource.new
157
- record.state = 'parked'
158
- record.errors.add(:state, 'is invalid')
159
-
160
- @machine.reset(record)
161
- assert_nil record.errors.on(:id)
162
- end
163
-
164
150
  def test_should_not_override_the_column_reader
165
151
  record = @resource.new
166
152
  record.attribute_set(:state, 'parked')
@@ -208,16 +194,10 @@ begin
208
194
  @record = @resource.new
209
195
  end
210
196
 
211
- def test_should_not_define_a_reader_attribute_for_the_attribute
212
- assert !@record.respond_to?(:status)
213
- end
214
-
215
- def test_should_not_define_a_writer_attribute_for_the_attribute
216
- assert !@record.respond_to?(:status=)
217
- end
218
-
219
- def test_should_define_an_attribute_predicate
220
- assert @record.respond_to?(:status?)
197
+ def test_should_define_a_new_property_for_the_attribute
198
+ assert_not_nil @resource.properties[:status]
199
+ assert @record.respond_to?(:status)
200
+ assert @record.respond_to?(:status=)
221
201
  end
222
202
  end
223
203
 
@@ -256,6 +236,7 @@ begin
256
236
  @resource = new_resource
257
237
  @machine = StateMachine::Machine.new(@resource)
258
238
  @machine.state :parked, :idling
239
+ @machine.event :ignite
259
240
  @record = @resource.new(:state => 'parked')
260
241
  @transition = StateMachine::Transition.new(@record, @machine, :ignite, :parked, :idling)
261
242
  end
@@ -268,7 +249,7 @@ begin
268
249
  assert called
269
250
  end
270
251
 
271
- def test_should_pass_transition_into_before_callbacks_with_one_argument
252
+ def test_should_pass_transition_to_before_callbacks_with_one_argument
272
253
  transition = nil
273
254
  @machine.before_transition(lambda {|arg| transition = arg})
274
255
 
@@ -276,7 +257,7 @@ begin
276
257
  assert_equal @transition, transition
277
258
  end
278
259
 
279
- def test_should_pass_transition_into_before_callbacks_with_multiple_arguments
260
+ def test_should_pass_transition_to_before_callbacks_with_multiple_arguments
280
261
  callback_args = nil
281
262
  @machine.before_transition(lambda {|*args| callback_args = args})
282
263
 
@@ -300,12 +281,12 @@ begin
300
281
  assert called
301
282
  end
302
283
 
303
- def test_should_pass_transition_and_result_into_after_callbacks_with_multiple_arguments
284
+ def test_should_pass_transition_to_after_callbacks_with_multiple_arguments
304
285
  callback_args = nil
305
286
  @machine.after_transition(lambda {|*args| callback_args = args})
306
287
 
307
288
  @transition.perform
308
- assert_equal [@transition, true], callback_args
289
+ assert_equal [@transition], callback_args
309
290
  end
310
291
 
311
292
  def test_should_run_after_callbacks_with_the_context_of_the_record
@@ -351,6 +332,7 @@ begin
351
332
  @resource = new_resource
352
333
  @machine = StateMachine::Machine.new(@resource)
353
334
  @machine.state :parked, :idling
335
+ @machine.event :ignite
354
336
  @record = @resource.new(:state => 'parked')
355
337
  @transition = StateMachine::Transition.new(@record, @machine, :ignite, :parked, :idling)
356
338
  end
@@ -391,28 +373,6 @@ begin
391
373
  assert !called
392
374
  end
393
375
 
394
- def test_should_allow_targeting_specific_machine
395
- @second_machine = StateMachine::Machine.new(@resource, :status)
396
-
397
- called_state = false
398
- called_status = false
399
-
400
- observer = new_observer(@resource) do
401
- before_transition :state, :from => :parked do
402
- called_state = true
403
- end
404
-
405
- before_transition :status, :from => :parked do
406
- called_status = true
407
- end
408
- end
409
-
410
- @transition.perform
411
-
412
- assert called_state
413
- assert !called_status
414
- end
415
-
416
376
  def test_should_pass_transition_to_before_callbacks
417
377
  callback_args = nil
418
378
 
@@ -452,7 +412,7 @@ begin
452
412
  assert !called
453
413
  end
454
414
 
455
- def test_should_pass_transition_and_result_to_before_callbacks
415
+ def test_should_pass_transition_to_after_callbacks
456
416
  callback_args = nil
457
417
 
458
418
  observer = new_observer(@resource) do
@@ -462,7 +422,61 @@ begin
462
422
  end
463
423
 
464
424
  @transition.perform
465
- assert_equal [@transition, true], callback_args
425
+ assert_equal [@transition], callback_args
426
+ end
427
+
428
+ def test_should_raise_exception_if_targeting_invalid_machine
429
+ assert_raise(RUBY_VERSION < '1.9' ? IndexError : KeyError) do
430
+ new_observer(@resource) do
431
+ before_transition :invalid, :from => :parked do
432
+ end
433
+ end
434
+ end
435
+ end
436
+
437
+ def test_should_allow_targeting_specific_machine
438
+ @second_machine = StateMachine::Machine.new(@resource, :status)
439
+ @resource.auto_migrate!
440
+
441
+ called_state = false
442
+ called_status = false
443
+
444
+ observer = new_observer(@resource) do
445
+ before_transition :state, :from => :parked do
446
+ called_state = true
447
+ end
448
+
449
+ before_transition :status, :from => :parked do
450
+ called_status = true
451
+ end
452
+ end
453
+
454
+ @transition.perform
455
+
456
+ assert called_state
457
+ assert !called_status
458
+ end
459
+
460
+ def test_should_allow_targeting_multiple_specific_machines
461
+ @second_machine = StateMachine::Machine.new(@resource, :status)
462
+ @second_machine.state :parked, :idling
463
+ @second_machine.event :ignite
464
+ @resource.auto_migrate!
465
+
466
+ called_attribute = nil
467
+
468
+ attributes = []
469
+ observer = new_observer(@resource) do
470
+ before_transition :state, :status, :from => :parked do |transition|
471
+ called_attribute = transition.attribute
472
+ end
473
+ end
474
+
475
+ @transition.perform
476
+ assert_equal :state, called_attribute
477
+
478
+ StateMachine::Transition.new(@record, @second_machine, :ignite, :parked, :idling).perform
479
+ assert_equal :status, called_attribute
466
480
  end
467
481
  end
468
482
 
@@ -471,6 +485,7 @@ begin
471
485
  @resource = new_resource
472
486
  @machine = StateMachine::Machine.new(@resource)
473
487
  @machine.state :parked, :idling
488
+ @machine.event :ignite
474
489
  @record = @resource.new(:state => 'parked')
475
490
  @transition = StateMachine::Transition.new(@record, @machine, :ignite, :parked, :idling)
476
491
 
@@ -512,6 +527,32 @@ begin
512
527
  gem 'dm-validations', ENV['DM_VERSION'] ? "=#{ENV['DM_VERSION']}" : '>=0.9.0'
513
528
  require 'dm-validations'
514
529
 
530
+ class MachineWithValidationsTest < BaseTestCase
531
+ def setup
532
+ @resource = new_resource
533
+ @machine = StateMachine::Machine.new(@resource)
534
+ @machine.state :parked
535
+ end
536
+
537
+ def test_should_invalidate_using_errors
538
+ record = @resource.new
539
+ record.state = 'parked'
540
+
541
+ @machine.invalidate(record, :state, :invalid_transition, [[:event, :park]])
542
+
543
+ assert_equal ['cannot transition via "park"'], record.errors.on(:state)
544
+ end
545
+
546
+ def test_should_clear_errors_on_reset
547
+ record = @resource.new
548
+ record.state = 'parked'
549
+ record.errors.add(:state, 'is invalid')
550
+
551
+ @machine.reset(record)
552
+ assert_nil record.errors.on(:id)
553
+ end
554
+ end
555
+
515
556
  class MachineWithStateDrivenValidationsTest < BaseTestCase
516
557
  def setup
517
558
  @resource = new_resource do
@@ -540,6 +581,57 @@ begin
540
581
  assert record.valid?
541
582
  end
542
583
  end
584
+
585
+ class MachineWithEventAttributesOnValidationTest < BaseTestCase
586
+ def setup
587
+ @resource = new_resource
588
+ @machine = StateMachine::Machine.new(@resource)
589
+ @machine.event :ignite do
590
+ transition :parked => :idling
591
+ end
592
+
593
+ @record = @resource.new
594
+ @record.state = 'parked'
595
+ @record.state_event = 'ignite'
596
+ end
597
+
598
+ def test_should_fail_if_event_is_invalid
599
+ @record.state_event = 'invalid'
600
+ assert !@record.valid?
601
+ assert_equal ['is invalid'], @record.errors.full_messages
602
+ end
603
+
604
+ def test_should_fail_if_event_has_no_transition
605
+ @record.state = 'idling'
606
+ assert !@record.valid?
607
+ assert_equal ['cannot transition when idling'], @record.errors.full_messages
608
+ end
609
+
610
+ def test_should_be_successful_if_event_has_transition
611
+ assert @record.valid?
612
+ end
613
+
614
+ def test_should_run_before_callbacks
615
+ ran_callback = false
616
+ @machine.before_transition { ran_callback = true }
617
+
618
+ @record.valid?
619
+ assert ran_callback
620
+ end
621
+
622
+ def test_should_persist_new_state
623
+ @record.valid?
624
+ assert_equal 'idling', @record.state
625
+ end
626
+
627
+ def test_should_not_run_after_callbacks
628
+ ran_callback = false
629
+ @machine.after_transition { ran_callback = true }
630
+
631
+ @record.valid?
632
+ assert !ran_callback
633
+ end
634
+ end
543
635
  rescue LoadError
544
636
  $stderr.puts "Skipping DataMapper Validation tests. `gem install dm-validations#{" -v #{ENV['DM_VERSION']}" if ENV['DM_VERSION']}` and try again."
545
637
  end
@@ -25,6 +25,7 @@ begin
25
25
  end if auto_migrate
26
26
  model = Class.new(Sequel::Model(:foo)) do
27
27
  self.raise_on_save_failure = false
28
+ plugin :validation_class_methods if respond_to?(:plugin)
28
29
 
29
30
  def self.name; 'SequelTest::Foo'; end
30
31
  end
@@ -52,6 +53,10 @@ begin
52
53
  def test_should_use_save_as_action
53
54
  assert_equal :save, @machine.action
54
55
  end
56
+
57
+ def test_should_use_transactions
58
+ assert_equal true, @machine.use_transactions
59
+ end
55
60
  end
56
61
 
57
62
  class MachineTest < BaseTestCase
@@ -135,9 +140,9 @@ begin
135
140
  record = @model.new
136
141
  record.state = 'parked'
137
142
 
138
- @machine.invalidate(record, StateMachine::Event.new(@machine, :park))
143
+ @machine.invalidate(record, :state, :invalid_transition, [[:event, :park]])
139
144
 
140
- assert_equal ['cannot be transitioned via :park from :parked'], record.errors.on(:state)
145
+ assert_equal ['cannot transition via "park"'], record.errors.on(:state)
141
146
  end
142
147
 
143
148
  def test_should_clear_errors_on_reset
@@ -244,6 +249,7 @@ begin
244
249
  @model = new_model
245
250
  @machine = StateMachine::Machine.new(@model)
246
251
  @machine.state :parked, :idling
252
+ @machine.event :ignite
247
253
  @record = @model.new(:state => 'parked')
248
254
  @transition = StateMachine::Transition.new(@record, @machine, :ignite, :parked, :idling)
249
255
  end
@@ -256,7 +262,7 @@ begin
256
262
  assert called
257
263
  end
258
264
 
259
- def test_should_pass_transition_into_before_callbacks_with_one_argument
265
+ def test_should_pass_transition_to_before_callbacks_with_one_argument
260
266
  transition = nil
261
267
  @machine.before_transition(lambda {|arg| transition = arg})
262
268
 
@@ -264,7 +270,7 @@ begin
264
270
  assert_equal @transition, transition
265
271
  end
266
272
 
267
- def test_should_pass_transition_into_before_callbacks_with_multiple_arguments
273
+ def test_should_pass_transition_to_before_callbacks_with_multiple_arguments
268
274
  callback_args = nil
269
275
  @machine.before_transition(lambda {|*args| callback_args = args})
270
276
 
@@ -288,12 +294,12 @@ begin
288
294
  assert called
289
295
  end
290
296
 
291
- def test_should_pass_transition_and_result_into_after_callbacks_with_multiple_arguments
297
+ def test_should_pass_transition_to_after_callbacks_with_multiple_arguments
292
298
  callback_args = nil
293
299
  @machine.after_transition(lambda {|*args| callback_args = args})
294
300
 
295
301
  @transition.perform
296
- assert_equal [@transition, true], callback_args
302
+ assert_equal [@transition], callback_args
297
303
  end
298
304
 
299
305
  def test_should_run_after_callbacks_with_the_context_of_the_record
@@ -358,6 +364,57 @@ begin
358
364
  assert record.valid?
359
365
  end
360
366
  end
367
+
368
+ class MachineWithEventAttributesOnValidationTest < BaseTestCase
369
+ def setup
370
+ @model = new_model
371
+ @machine = StateMachine::Machine.new(@model)
372
+ @machine.event :ignite do
373
+ transition :parked => :idling
374
+ end
375
+
376
+ @record = @model.new
377
+ @record.state = 'parked'
378
+ @record.state_event = 'ignite'
379
+ end
380
+
381
+ def test_should_fail_if_event_is_invalid
382
+ @record.state_event = 'invalid'
383
+ assert !@record.valid?
384
+ assert_equal ['state_event is invalid'], @record.errors.full_messages
385
+ end
386
+
387
+ def test_should_fail_if_event_has_no_transition
388
+ @record.state = 'idling'
389
+ assert !@record.valid?
390
+ assert_equal ['state_event cannot transition when idling'], @record.errors.full_messages
391
+ end
392
+
393
+ def test_should_be_successful_if_event_has_transition
394
+ assert @record.valid?
395
+ end
396
+
397
+ def test_should_run_before_callbacks
398
+ ran_callback = false
399
+ @machine.before_transition { ran_callback = true }
400
+
401
+ @record.valid?
402
+ assert ran_callback
403
+ end
404
+
405
+ def test_should_persist_new_state
406
+ @record.valid?
407
+ assert_equal 'idling', @record.state
408
+ end
409
+
410
+ def test_should_not_run_after_callbacks
411
+ ran_callback = false
412
+ @machine.after_transition { ran_callback = true }
413
+
414
+ @record.valid?
415
+ assert !ran_callback
416
+ end
417
+ end
361
418
  end
362
419
  rescue LoadError
363
420
  $stderr.puts "Skipping Sequel tests. `gem install sequel#{" -v #{ENV['SEQUEL_VERSION']}" if ENV['SEQUEL_VERSION']}` and try again."
@@ -0,0 +1,7 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../test_helper')
2
+
3
+ class InvalidEventTest < Test::Unit::TestCase
4
+ def test_should_exist
5
+ assert_not_nil StateMachine::InvalidEvent
6
+ end
7
+ end