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
@@ -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