state_machine 0.6.3 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -39,6 +39,10 @@ class MachineByDefaultTest < Test::Unit::TestCase
39
39
  assert_nil @machine.action
40
40
  end
41
41
 
42
+ def test_should_use_tranactions
43
+ assert_equal true, @machine.use_transactions
44
+ end
45
+
42
46
  def test_should_not_have_a_namespace
43
47
  assert_nil @machine.namespace
44
48
  end
@@ -79,6 +83,30 @@ class MachineByDefaultTest < Test::Unit::TestCase
79
83
  assert @object.respond_to?(:state_name)
80
84
  end
81
85
 
86
+ def test_should_define_an_event_reader_for_the_attribute
87
+ assert @object.respond_to?(:state_events)
88
+ end
89
+
90
+ def test_should_define_a_transition_reader_for_the_attribute
91
+ assert @object.respond_to?(:state_transitions)
92
+ end
93
+
94
+ def test_should_not_define_an_event_attribute_reader
95
+ assert !@object.respond_to?(:state_event)
96
+ end
97
+
98
+ def test_should_not_define_an_event_attribute_writer
99
+ assert !@object.respond_to?(:state_event=)
100
+ end
101
+
102
+ def test_should_not_define_an_event_transition_attribute_reader
103
+ assert !@object.respond_to?(:state_event_transition)
104
+ end
105
+
106
+ def test_should_not_define_an_event_transition_attribute_writer
107
+ assert !@object.respond_to?(:state_event_transition=)
108
+ end
109
+
82
110
  def test_should_not_define_singular_with_scope
83
111
  assert !@klass.respond_to?(:with_state)
84
112
  end
@@ -135,6 +163,14 @@ class MachineWithCustomAttributeTest < Test::Unit::TestCase
135
163
  def test_should_define_a_name_reader_for_the_attribute
136
164
  assert @object.respond_to?(:status_name)
137
165
  end
166
+
167
+ def test_should_define_an_event_reader_for_the_attribute
168
+ assert @object.respond_to?(:status_events)
169
+ end
170
+
171
+ def test_should_define_a_transition_reader_for_the_attribute
172
+ assert @object.respond_to?(:status_transitions)
173
+ end
138
174
  end
139
175
 
140
176
  class MachineWithStaticInitialStateTest < Test::Unit::TestCase
@@ -218,9 +254,8 @@ end
218
254
  class MachineWithNilActionTest < Test::Unit::TestCase
219
255
  def setup
220
256
  integration = Module.new do
221
- def default_action
222
- :save
223
- end
257
+ class << self; attr_reader :defaults; end
258
+ @defaults = {:action => :save}
224
259
  end
225
260
  StateMachine::Integrations.const_set('Custom', integration)
226
261
  @machine = StateMachine::Machine.new(Class.new, :action => nil, :integration => :custom)
@@ -252,7 +287,7 @@ class MachineWithoutIntegrationTest < Test::Unit::TestCase
252
287
  end
253
288
 
254
289
  def test_invalidation_should_do_nothing
255
- assert_nil @machine.invalidate(@object, StateMachine::Event.new(@machine, :park))
290
+ assert_nil @machine.invalidate(@object, :state, :invalid_transition, [[:event, :park]])
256
291
  end
257
292
 
258
293
  def test_reset_should_do_nothing
@@ -277,37 +312,37 @@ end
277
312
 
278
313
  class MachineWithIntegrationTest < Test::Unit::TestCase
279
314
  def setup
280
- @integration = Module.new do
281
- class << self; attr_accessor :initialized, :with_scopes, :without_scopes; end
282
- @initialized = false
283
- @with_scopes = []
284
- @without_scopes = []
315
+ StateMachine::Integrations.const_set('Custom', Module.new do
316
+ class << self; attr_reader :defaults; end
317
+ @defaults = {:action => :save, :use_transactions => false}
318
+
319
+ attr_reader :initialized, :with_scopes, :without_scopes, :ran_transaction
285
320
 
286
321
  def after_initialize
287
- StateMachine::Integrations::Custom.initialized = true
288
- end
289
-
290
- def default_action
291
- :save
322
+ @initialized = true
292
323
  end
293
324
 
294
325
  def create_with_scope(name)
295
- StateMachine::Integrations::Custom.with_scopes << name
326
+ (@with_scopes ||= []) << name
296
327
  lambda {}
297
328
  end
298
329
 
299
330
  def create_without_scope(name)
300
- StateMachine::Integrations::Custom.without_scopes << name
331
+ (@without_scopes ||= []) << name
301
332
  lambda {}
302
333
  end
303
- end
334
+
335
+ def transaction(object)
336
+ @ran_transaction = true
337
+ yield
338
+ end
339
+ end)
304
340
 
305
- StateMachine::Integrations.const_set('Custom', @integration)
306
341
  @machine = StateMachine::Machine.new(Class.new, :integration => :custom)
307
342
  end
308
343
 
309
344
  def test_should_call_after_initialize_hook
310
- assert @integration.initialized
345
+ assert @machine.initialized
311
346
  end
312
347
 
313
348
  def test_should_use_the_default_action
@@ -319,12 +354,21 @@ class MachineWithIntegrationTest < Test::Unit::TestCase
319
354
  assert_equal :save!, machine.action
320
355
  end
321
356
 
357
+ def test_should_use_the_default_use_transactions
358
+ assert_equal false, @machine.use_transactions
359
+ end
360
+
361
+ def test_should_use_the_custom_use_transactions_if_specified
362
+ machine = StateMachine::Machine.new(Class.new, :integration => :custom, :use_transactions => true)
363
+ assert_equal true, machine.use_transactions
364
+ end
365
+
322
366
  def test_should_define_a_singular_and_plural_with_scope
323
- assert_equal %w(with_state with_states), @integration.with_scopes
367
+ assert_equal %w(with_state with_states), @machine.with_scopes
324
368
  end
325
369
 
326
370
  def test_should_define_a_singular_and_plural_without_scope
327
- assert_equal %w(without_state without_states), @integration.without_scopes
371
+ assert_equal %w(without_state without_states), @machine.without_scopes
328
372
  end
329
373
 
330
374
  def teardown
@@ -332,6 +376,62 @@ class MachineWithIntegrationTest < Test::Unit::TestCase
332
376
  end
333
377
  end
334
378
 
379
+ class MachineWithActionTest < Test::Unit::TestCase
380
+ def setup
381
+ @klass = Class.new do
382
+ def save
383
+ end
384
+ end
385
+
386
+ @machine = StateMachine::Machine.new(@klass, :action => :save)
387
+ @object = @klass.new
388
+ end
389
+
390
+ def test_should_define_an_event_attribute_reader
391
+ assert @object.respond_to?(:state_event)
392
+ end
393
+
394
+ def test_should_define_an_event_attribute_writer
395
+ assert @object.respond_to?(:state_event=)
396
+ end
397
+
398
+ def test_should_define_an_event_transition_attribute_reader
399
+ assert @object.respond_to?(:state_event_transition)
400
+ end
401
+
402
+ def test_should_define_an_event_transition_attribute_writer
403
+ assert @object.respond_to?(:state_event_transition=)
404
+ end
405
+ end
406
+
407
+ class MachineWithActionUndefinedTest < Test::Unit::TestCase
408
+ def setup
409
+ @klass = Class.new
410
+ @machine = StateMachine::Machine.new(@klass, :action => :save)
411
+ @object = @klass.new
412
+ end
413
+
414
+ def test_should_define_an_event_attribute_reader
415
+ assert @object.respond_to?(:state_event)
416
+ end
417
+
418
+ def test_should_define_an_event_attribute_writer
419
+ assert @object.respond_to?(:state_event=)
420
+ end
421
+
422
+ def test_should_define_an_event_transition_attribute_reader
423
+ assert @object.respond_to?(:state_event_transition)
424
+ end
425
+
426
+ def test_should_define_an_event_transition_attribute_writer
427
+ assert @object.respond_to?(:state_event_transition=)
428
+ end
429
+
430
+ def test_should_not_define_action
431
+ assert !@object.respond_to?(:save)
432
+ end
433
+ end
434
+
335
435
  class MachineWithCustomPluralTest < Test::Unit::TestCase
336
436
  def setup
337
437
  @integration = Module.new do
@@ -370,8 +470,8 @@ end
370
470
  class MachineWithCustomInvalidationTest < Test::Unit::TestCase
371
471
  def setup
372
472
  @integration = Module.new do
373
- def invalidate(object, event)
374
- object.error = invalid_message(object, event)
473
+ def invalidate(object, attribute, message, values = [])
474
+ object.error = generate_message(message, values)
375
475
  end
376
476
  end
377
477
  StateMachine::Integrations.const_set('Custom', @integration)
@@ -380,7 +480,7 @@ class MachineWithCustomInvalidationTest < Test::Unit::TestCase
380
480
  attr_accessor :error
381
481
  end
382
482
 
383
- @machine = StateMachine::Machine.new(@klass, :integration => :custom, :invalid_message => 'cannot %s when %s')
483
+ @machine = StateMachine::Machine.new(@klass, :integration => :custom, :messages => {:invalid_transition => 'cannot %s'})
384
484
  @machine.state :parked
385
485
 
386
486
  @object = @klass.new
@@ -388,8 +488,8 @@ class MachineWithCustomInvalidationTest < Test::Unit::TestCase
388
488
  end
389
489
 
390
490
  def test_use_custom_message
391
- @machine.invalidate(@object, StateMachine::Event.new(@machine, :park))
392
- assert_equal 'cannot park when parked', @object.error
491
+ @machine.invalidate(@object, :state, :invalid_transition, [[:event, :park]])
492
+ assert_equal 'cannot park', @object.error
393
493
  end
394
494
 
395
495
  def teardown
@@ -402,8 +502,8 @@ class MachineTest < Test::Unit::TestCase
402
502
  assert_raise(ArgumentError) {StateMachine::Machine.new(Class.new, :invalid => true)}
403
503
  end
404
504
 
405
- def test_should_not_raise_exception_if_custom_invalid_message_specified
406
- assert_nothing_raised {StateMachine::Machine.new(Class.new, :invalid_message => 'custom')}
505
+ def test_should_not_raise_exception_if_custom_messages_specified
506
+ assert_nothing_raised {StateMachine::Machine.new(Class.new, :messages => {:invalid_transition => 'custom'})}
407
507
  end
408
508
 
409
509
  def test_should_evaluate_a_block_during_initialization
@@ -667,6 +767,14 @@ class MachineWithConflictingHelpersTest < Test::Unit::TestCase
667
767
  def state_name
668
768
  :parked
669
769
  end
770
+
771
+ def state_events
772
+ [:ignite]
773
+ end
774
+
775
+ def state_transitions
776
+ [{:parked => :idling}]
777
+ end
670
778
  end
671
779
 
672
780
  StateMachine::Integrations.const_set('Custom', Module.new do
@@ -717,6 +825,14 @@ class MachineWithConflictingHelpersTest < Test::Unit::TestCase
717
825
  assert_equal :parked, @object.state_name
718
826
  end
719
827
 
828
+ def test_should_not_redefine_attribute_events_reader
829
+ assert_equal [:ignite], @object.state_events
830
+ end
831
+
832
+ def test_should_not_redefine_attribute_transitions_reader
833
+ assert_equal [{:parked => :idling}], @object.state_transitions
834
+ end
835
+
720
836
  def test_should_allow_super_chaining
721
837
  @klass.class_eval do
722
838
  def self.with_state(*states)
@@ -753,6 +869,14 @@ class MachineWithConflictingHelpersTest < Test::Unit::TestCase
753
869
  def state_name
754
870
  super == :parked ? 1 : 0
755
871
  end
872
+
873
+ def state_events
874
+ super == []
875
+ end
876
+
877
+ def state_transitions
878
+ super == []
879
+ end
756
880
  end
757
881
 
758
882
  assert_equal true, @klass.with_state
@@ -765,6 +889,8 @@ class MachineWithConflictingHelpersTest < Test::Unit::TestCase
765
889
  assert_equal 'idling', @object.status
766
890
  assert_equal 0, @object.state?(:parked)
767
891
  assert_equal 0, @object.state_name
892
+ assert_equal true, @object.state_events
893
+ assert_equal true, @object.state_transitions
768
894
  end
769
895
 
770
896
  def teardown
@@ -860,6 +986,23 @@ class MachineWithCustomInitializeTest < Test::Unit::TestCase
860
986
  end
861
987
  end
862
988
 
989
+ class MachinePersistenceTest < Test::Unit::TestCase
990
+ def setup
991
+ @klass = Class.new
992
+ @machine = StateMachine::Machine.new(@klass, :initial => :parked)
993
+ @object = @klass.new
994
+ end
995
+
996
+ def test_should_allow_reading_state
997
+ assert_equal 'parked', @machine.read(@object)
998
+ end
999
+
1000
+ def test_should_allow_writing_state
1001
+ @machine.write(@object, 'idling')
1002
+ assert_equal 'idling', @object.state
1003
+ end
1004
+ end
1005
+
863
1006
  class MachineWithStatesTest < Test::Unit::TestCase
864
1007
  def setup
865
1008
  @klass = Class.new
@@ -893,30 +1036,6 @@ class MachineWithStatesTest < Test::Unit::TestCase
893
1036
  exception = assert_raise(ArgumentError) {@machine.state(:first_gear, :invalid => true)}
894
1037
  assert_equal 'Invalid key(s): invalid', exception.message
895
1038
  end
896
-
897
- def test_should_not_be_in_state_if_value_does_not_match
898
- assert !@machine.state?(@object, :parked)
899
- assert !@machine.state?(@object, :idling)
900
- end
901
-
902
- def test_should_be_in_state_if_value_matches
903
- assert @machine.state?(@object, nil)
904
- end
905
-
906
- def test_raise_exception_if_checking_invalid_state
907
- assert_raise(ArgumentError) { @machine.state?(@object, :invalid) }
908
- end
909
-
910
- def test_should_find_state_for_object_if_value_is_known
911
- @object.state = 'parked'
912
- assert_equal @parked, @machine.state_for(@object)
913
- end
914
-
915
- def test_should_raise_exception_if_finding_state_for_object_with_unknown_value
916
- @object.state = 'invalid'
917
- exception = assert_raise(ArgumentError) { @machine.state_for(@object) }
918
- assert_equal '"invalid" is not a known state value', exception.message
919
- end
920
1039
  end
921
1040
 
922
1041
  class MachineWithStatesWithCustomValuesTest < Test::Unit::TestCase
@@ -936,19 +1055,6 @@ class MachineWithStatesWithCustomValuesTest < Test::Unit::TestCase
936
1055
  def test_should_allow_lookup_by_custom_value
937
1056
  assert_equal @state, @machine.states[1, :value]
938
1057
  end
939
-
940
- def test_should_be_in_state_if_value_matches
941
- assert @machine.state?(@object, :parked)
942
- end
943
-
944
- def test_should_not_be_in_state_if_value_does_not_match
945
- @object.state = 2
946
- assert !@machine.state?(@object, :parked)
947
- end
948
-
949
- def test_should_find_state_for_object_if_value_is_known
950
- assert_equal @state, @machine.state_for(@object)
951
- end
952
1058
  end
953
1059
 
954
1060
  class MachineWithStateWithMatchersTest < Test::Unit::TestCase
@@ -966,19 +1072,6 @@ class MachineWithStateWithMatchersTest < Test::Unit::TestCase
966
1072
  assert @state.matches?(1)
967
1073
  assert !@state.matches?(nil)
968
1074
  end
969
-
970
- def test_should_be_in_state_if_value_matches
971
- assert @machine.state?(@object, :parked)
972
- end
973
-
974
- def test_should_not_be_in_state_if_value_does_not_match
975
- @object.state = nil
976
- assert !@machine.state?(@object, :parked)
977
- end
978
-
979
- def test_should_find_state_for_object_if_value_is_known
980
- assert_equal @state, @machine.state_for(@object)
981
- end
982
1075
  end
983
1076
 
984
1077
  class MachineWithStatesWithBehaviorsTest < Test::Unit::TestCase
@@ -1050,7 +1143,8 @@ end
1050
1143
 
1051
1144
  class MachineWithEventsTest < Test::Unit::TestCase
1052
1145
  def setup
1053
- @machine = StateMachine::Machine.new(Class.new)
1146
+ @klass = Class.new
1147
+ @machine = StateMachine::Machine.new(@klass)
1054
1148
  end
1055
1149
 
1056
1150
  def test_should_return_the_created_event
@@ -1309,44 +1403,44 @@ end
1309
1403
  class MachineWithNamespaceTest < Test::Unit::TestCase
1310
1404
  def setup
1311
1405
  @klass = Class.new
1312
- @machine = StateMachine::Machine.new(@klass, :namespace => 'car', :initial => :parked) do
1313
- event :ignite do
1314
- transition :parked => :idling
1406
+ @machine = StateMachine::Machine.new(@klass, :namespace => 'alarm', :initial => :active) do
1407
+ event :enable do
1408
+ transition :off => :active
1315
1409
  end
1316
1410
 
1317
- event :park do
1318
- transition :idling => :parked
1411
+ event :disable do
1412
+ transition :active => :off
1319
1413
  end
1320
1414
  end
1321
1415
  @object = @klass.new
1322
1416
  end
1323
1417
 
1324
1418
  def test_should_namespace_state_predicates
1325
- [:car_parked?, :car_idling?].each do |name|
1419
+ [:alarm_active?, :alarm_off?].each do |name|
1326
1420
  assert @object.respond_to?(name)
1327
1421
  end
1328
1422
  end
1329
1423
 
1330
1424
  def test_should_namespace_event_checks
1331
- [:can_ignite_car?, :can_park_car?].each do |name|
1425
+ [:can_enable_alarm?, :can_disable_alarm?].each do |name|
1332
1426
  assert @object.respond_to?(name)
1333
1427
  end
1334
1428
  end
1335
1429
 
1336
1430
  def test_should_namespace_event_transition_readers
1337
- [:next_ignite_car_transition, :next_park_car_transition].each do |name|
1431
+ [:enable_alarm_transition, :disable_alarm_transition].each do |name|
1338
1432
  assert @object.respond_to?(name)
1339
1433
  end
1340
1434
  end
1341
1435
 
1342
1436
  def test_should_namespace_events
1343
- [:ignite_car, :park_car].each do |name|
1437
+ [:enable_alarm, :disable_alarm].each do |name|
1344
1438
  assert @object.respond_to?(name)
1345
1439
  end
1346
1440
  end
1347
1441
 
1348
1442
  def test_should_namespace_bang_events
1349
- [:ignite_car!, :park_car!].each do |name|
1443
+ [:enable_alarm!, :disable_alarm!].each do |name|
1350
1444
  assert @object.respond_to?(name)
1351
1445
  end
1352
1446
  end
@@ -1413,7 +1507,7 @@ class MachineFinderWithExistingMachineOnSuperclassTest < Test::Unit::TestCase
1413
1507
  @base_machine.after_transition(lambda {})
1414
1508
 
1415
1509
  @klass = Class.new(@base_class)
1416
- @machine = StateMachine::Machine.find_or_create(@klass, :status)
1510
+ @machine = StateMachine::Machine.find_or_create(@klass, :status) {}
1417
1511
  end
1418
1512
 
1419
1513
  def test_should_accept_a_block
@@ -1425,7 +1519,20 @@ class MachineFinderWithExistingMachineOnSuperclassTest < Test::Unit::TestCase
1425
1519
  assert called
1426
1520
  end
1427
1521
 
1428
- def test_should_create_a_new_machine
1522
+ def test_should_not_create_a_new_machine_if_no_block_or_options
1523
+ machine = StateMachine::Machine.find_or_create(Class.new(@base_class), :status)
1524
+
1525
+ assert_same machine, @base_machine
1526
+ end
1527
+
1528
+ def test_should_create_a_new_machine_if_given_options
1529
+ machine = StateMachine::Machine.find_or_create(@klass, :status, :initial => :parked)
1530
+
1531
+ assert_not_nil machine
1532
+ assert_not_same machine, @base_machine
1533
+ end
1534
+
1535
+ def test_should_create_a_new_machine_if_given_block
1429
1536
  assert_not_nil @machine
1430
1537
  assert_not_same @machine, @base_machine
1431
1538
  end