state_machines-activerecord 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (69) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +22 -0
  3. data/.travis.yml +20 -0
  4. data/Appraisals +11 -0
  5. data/Gemfile +6 -0
  6. data/LICENSE.txt +23 -0
  7. data/README.md +65 -0
  8. data/Rakefile +9 -0
  9. data/gemfiles/active_record_4.1.gemfile +13 -0
  10. data/gemfiles/active_record_4.2.gemfile +13 -0
  11. data/lib/state_machines-activerecord.rb +1 -0
  12. data/lib/state_machines/integrations/active_record.rb +588 -0
  13. data/lib/state_machines/integrations/active_record/locale.rb +12 -0
  14. data/lib/state_machines/integrations/active_record/version.rb +7 -0
  15. data/log/.gitkeep +0 -0
  16. data/state_machines-activerecord.gemspec +27 -0
  17. data/test/files/en.yml +5 -0
  18. data/test/files/models/post.rb +11 -0
  19. data/test/integration_test.rb +23 -0
  20. data/test/machine_by_default_test.rb +16 -0
  21. data/test/machine_errors_test.rb +19 -0
  22. data/test/machine_multiple_test.rb +17 -0
  23. data/test/machine_nested_action_test.rb +38 -0
  24. data/test/machine_unmigrated_test.rb +14 -0
  25. data/test/machine_with_aliased_attribute_test.rb +23 -0
  26. data/test/machine_with_callbacks_test.rb +172 -0
  27. data/test/machine_with_column_state_attribute_test.rb +44 -0
  28. data/test/machine_with_complex_pluralization_scopes_test.rb +16 -0
  29. data/test/machine_with_conflicting_predicate_test.rb +18 -0
  30. data/test/machine_with_conflicting_state_name_test.rb +29 -0
  31. data/test/machine_with_custom_attribute_test.rb +21 -0
  32. data/test/machine_with_default_scope_test.rb +18 -0
  33. data/test/machine_with_different_column_default_test.rb +27 -0
  34. data/test/machine_with_different_integer_column_default_test.rb +29 -0
  35. data/test/machine_with_dirty_attribute_and_custom_attributes_during_loopback_test.rb +24 -0
  36. data/test/machine_with_dirty_attribute_and_state_events_test.rb +20 -0
  37. data/test/machine_with_dirty_attributes_and_custom_attribute_test.rb +32 -0
  38. data/test/machine_with_dirty_attributes_during_loopback_test.rb +22 -0
  39. data/test/machine_with_dirty_attributes_test.rb +35 -0
  40. data/test/machine_with_dynamic_initial_state_test.rb +99 -0
  41. data/test/machine_with_event_attributes_on_autosave_test.rb +48 -0
  42. data/test/machine_with_event_attributes_on_custom_action_test.rb +41 -0
  43. data/test/machine_with_event_attributes_on_save_bang_test.rb +82 -0
  44. data/test/machine_with_event_attributes_on_save_test.rb +184 -0
  45. data/test/machine_with_event_attributes_on_validation_test.rb +126 -0
  46. data/test/machine_with_events_test.rb +13 -0
  47. data/test/machine_with_failed_action_test.rb +40 -0
  48. data/test/machine_with_failed_after_callbacks_test.rb +35 -0
  49. data/test/machine_with_failed_before_callbacks_test.rb +36 -0
  50. data/test/machine_with_initialized_state_test.rb +35 -0
  51. data/test/machine_with_internationalization_test.rb +180 -0
  52. data/test/machine_with_loopback_test.rb +22 -0
  53. data/test/machine_with_non_column_state_attribute_defined_test.rb +35 -0
  54. data/test/machine_with_non_column_state_attribute_undefined_test.rb +33 -0
  55. data/test/machine_with_same_column_default_test.rb +26 -0
  56. data/test/machine_with_scopes_and_joins_test.rb +37 -0
  57. data/test/machine_with_scopes_and_owner_subclass_test.rb +27 -0
  58. data/test/machine_with_scopes_test.rb +70 -0
  59. data/test/machine_with_state_driven_validations_test.rb +30 -0
  60. data/test/machine_with_states_test.rb +13 -0
  61. data/test/machine_with_static_initial_state_test.rb +166 -0
  62. data/test/machine_with_transactions_test.rb +26 -0
  63. data/test/machine_with_validations_and_custom_attribute_test.rb +21 -0
  64. data/test/machine_with_validations_test.rb +47 -0
  65. data/test/machine_without_database_test.rb +20 -0
  66. data/test/machine_without_transactions_test.rb +26 -0
  67. data/test/model_test.rb +12 -0
  68. data/test/test_helper.rb +42 -0
  69. metadata +264 -0
@@ -0,0 +1,40 @@
1
+ require_relative 'test_helper'
2
+
3
+ class MachineWithFailedActionTest < BaseTestCase
4
+ def setup
5
+ @model = new_model do
6
+ validates_inclusion_of :state, :in => %w(first_gear)
7
+ end
8
+
9
+ @machine = StateMachines::Machine.new(@model)
10
+ @machine.state :parked, :idling
11
+ @machine.event :ignite
12
+
13
+ @callbacks = []
14
+ @machine.before_transition { @callbacks << :before }
15
+ @machine.after_transition { @callbacks << :after }
16
+ @machine.after_failure { @callbacks << :after_failure }
17
+ @machine.around_transition { |block| @callbacks << :around_before; block.call; @callbacks << :around_after }
18
+
19
+ @record = @model.new(:state => 'parked')
20
+ @transition = StateMachines::Transition.new(@record, @machine, :ignite, :parked, :idling)
21
+ @result = @transition.perform
22
+ end
23
+
24
+ def test_should_not_be_successful
25
+ refute @result
26
+ end
27
+
28
+ def test_should_not_change_current_state
29
+ assert_equal 'parked', @record.state
30
+ end
31
+
32
+ def test_should_not_save_record
33
+ assert @record.new_record?
34
+ end
35
+
36
+ def test_should_run_before_callbacks_and_after_callbacks_with_failures
37
+ assert_equal [:before, :around_before, :after_failure], @callbacks
38
+ end
39
+ end
40
+
@@ -0,0 +1,35 @@
1
+ require_relative 'test_helper'
2
+
3
+ class MachineWithFailedAfterCallbacksTest < BaseTestCase
4
+ def setup
5
+ @callbacks = []
6
+
7
+ @model = new_model
8
+ @machine = StateMachines::Machine.new(@model)
9
+ @machine.state :parked, :idling
10
+ @machine.event :ignite
11
+ @machine.after_transition { @callbacks << :after_1; false }
12
+ @machine.after_transition { @callbacks << :after_2 }
13
+ @machine.around_transition { |block| @callbacks << :around_before; block.call; @callbacks << :around_after }
14
+
15
+ @record = @model.new(:state => 'parked')
16
+ @transition = StateMachines::Transition.new(@record, @machine, :ignite, :parked, :idling)
17
+ @result = @transition.perform
18
+ end
19
+
20
+ def test_should_be_successful
21
+ assert @result
22
+ end
23
+
24
+ def test_should_change_current_state
25
+ assert_equal 'idling', @record.state
26
+ end
27
+
28
+ def test_should_save_record
29
+ refute @record.new_record?
30
+ end
31
+
32
+ def test_should_not_run_further_after_callbacks
33
+ assert_equal [:around_before, :around_after, :after_1], @callbacks
34
+ end
35
+ end
@@ -0,0 +1,36 @@
1
+ require_relative 'test_helper'
2
+
3
+ class MachineWithFailedBeforeCallbacksTest < BaseTestCase
4
+ def setup
5
+ @callbacks = []
6
+
7
+ @model = new_model
8
+ @machine = StateMachines::Machine.new(@model)
9
+ @machine.state :parked, :idling
10
+ @machine.event :ignite
11
+ @machine.before_transition { @callbacks << :before_1; false }
12
+ @machine.before_transition { @callbacks << :before_2 }
13
+ @machine.after_transition { @callbacks << :after }
14
+ @machine.around_transition { |block| @callbacks << :around_before; block.call; @callbacks << :around_after }
15
+
16
+ @record = @model.new(:state => 'parked')
17
+ @transition = StateMachines::Transition.new(@record, @machine, :ignite, :parked, :idling)
18
+ @result = @transition.perform
19
+ end
20
+
21
+ def test_should_not_be_successful
22
+ refute @result
23
+ end
24
+
25
+ def test_should_not_change_current_state
26
+ assert_equal 'parked', @record.state
27
+ end
28
+
29
+ def test_should_not_run_action
30
+ assert @record.new_record?
31
+ end
32
+
33
+ def test_should_not_run_further_callbacks
34
+ assert_equal [:before_1], @callbacks
35
+ end
36
+ end
@@ -0,0 +1,35 @@
1
+ require_relative 'test_helper'
2
+
3
+ class MachineWithInitializedStateTest < BaseTestCase
4
+ def setup
5
+ @model = new_model
6
+ @machine = StateMachines::Machine.new(@model, :initial => :parked)
7
+ @machine.state :idling
8
+ end
9
+
10
+ def test_should_allow_nil_initial_state_when_static
11
+ @machine.state nil
12
+
13
+ record = @model.new(:state => nil)
14
+ assert_nil record.state
15
+ end
16
+
17
+ def test_should_allow_nil_initial_state_when_dynamic
18
+ @machine.state nil
19
+
20
+ @machine.initial_state = lambda { :parked }
21
+ record = @model.new(:state => nil)
22
+ assert_nil record.state
23
+ end
24
+
25
+ def test_should_allow_different_initial_state_when_static
26
+ record = @model.new(:state => 'idling')
27
+ assert_equal 'idling', record.state
28
+ end
29
+
30
+ def test_should_allow_different_initial_state_when_dynamic
31
+ @machine.initial_state = lambda { :parked }
32
+ record = @model.new(:state => 'idling')
33
+ assert_equal 'idling', record.state
34
+ end
35
+ end
@@ -0,0 +1,180 @@
1
+ require_relative 'test_helper'
2
+
3
+ class MachineWithInternationalizationTest < BaseTestCase
4
+ def setup
5
+ I18n.backend = I18n::Backend::Simple.new
6
+
7
+ # Initialize the backend
8
+ StateMachines::Machine.new(new_model)
9
+
10
+ @model = new_model
11
+ end
12
+
13
+ def test_should_use_defaults
14
+ I18n.backend.store_translations(:en, {
15
+ :activerecord => {:errors => {:messages => {:invalid_transition => "cannot #{interpolation_key('event')}"}}}
16
+ })
17
+
18
+ machine = StateMachines::Machine.new(@model)
19
+ machine.state :parked, :idling
20
+ machine.event :ignite
21
+
22
+ record = @model.new(:state => 'idling')
23
+
24
+ machine.invalidate(record, :state, :invalid_transition, [[:event, 'ignite']])
25
+ assert_equal ['State cannot transition via "ignite"'], record.errors.full_messages
26
+ end
27
+
28
+ def test_should_allow_customized_error_key
29
+ I18n.backend.store_translations(:en, {
30
+ :activerecord => {:errors => {:messages => {:bad_transition => "cannot #{interpolation_key('event')}"}}}
31
+ })
32
+
33
+ machine = StateMachines::Machine.new(@model, :messages => {:invalid_transition => :bad_transition})
34
+ machine.state :parked, :idling
35
+
36
+ record = @model.new(:state => 'idling')
37
+
38
+ machine.invalidate(record, :state, :invalid_transition, [[:event, 'ignite']])
39
+ assert_equal ['State cannot ignite'], record.errors.full_messages
40
+ end
41
+
42
+ def test_should_allow_customized_error_string
43
+ machine = StateMachines::Machine.new(@model, :messages => {:invalid_transition => "cannot #{interpolation_key('event')}"})
44
+ machine.state :parked, :idling
45
+
46
+ record = @model.new(:state => 'idling')
47
+
48
+ machine.invalidate(record, :state, :invalid_transition, [[:event, 'ignite']])
49
+ assert_equal ['State cannot ignite'], record.errors.full_messages
50
+ end
51
+
52
+ def test_should_allow_customized_state_key_scoped_to_class_and_machine
53
+ I18n.backend.store_translations(:en, {
54
+ :activerecord => {:state_machines => {:'foo' => {:state => {:states => {:parked => 'shutdown'}}}}}
55
+ })
56
+
57
+ machine = StateMachines::Machine.new(@model)
58
+ machine.state :parked
59
+
60
+ assert_equal 'shutdown', machine.state(:parked).human_name
61
+ end
62
+
63
+ def test_should_allow_customized_state_key_scoped_to_class
64
+ I18n.backend.store_translations(:en, {
65
+ :activerecord => {:state_machines => {:'foo' => {:states => {:parked => 'shutdown'}}}}
66
+ })
67
+
68
+ machine = StateMachines::Machine.new(@model)
69
+ machine.state :parked
70
+
71
+ assert_equal 'shutdown', machine.state(:parked).human_name
72
+ end
73
+
74
+ def test_should_allow_customized_state_key_scoped_to_machine
75
+ I18n.backend.store_translations(:en, {
76
+ :activerecord => {:state_machines => {:state => {:states => {:parked => 'shutdown'}}}}
77
+ })
78
+
79
+ machine = StateMachines::Machine.new(@model)
80
+ machine.state :parked
81
+
82
+ assert_equal 'shutdown', machine.state(:parked).human_name
83
+ end
84
+
85
+ def test_should_allow_customized_state_key_unscoped
86
+ I18n.backend.store_translations(:en, {
87
+ :activerecord => {:state_machines => {:states => {:parked => 'shutdown'}}}
88
+ })
89
+
90
+ machine = StateMachines::Machine.new(@model)
91
+ machine.state :parked
92
+
93
+ assert_equal 'shutdown', machine.state(:parked).human_name
94
+ end
95
+
96
+ def test_should_support_nil_state_key
97
+ I18n.backend.store_translations(:en, {
98
+ :activerecord => {:state_machines => {:states => {:nil => 'empty'}}}
99
+ })
100
+
101
+ machine = StateMachines::Machine.new(@model)
102
+
103
+ assert_equal 'empty', machine.state(nil).human_name
104
+ end
105
+
106
+ def test_should_allow_customized_event_key_scoped_to_class_and_machine
107
+ I18n.backend.store_translations(:en, {
108
+ :activerecord => {:state_machines => {:'foo' => {:state => {:events => {:park => 'stop'}}}}}
109
+ })
110
+
111
+ machine = StateMachines::Machine.new(@model)
112
+ machine.event :park
113
+
114
+ assert_equal 'stop', machine.event(:park).human_name
115
+ end
116
+
117
+ def test_should_allow_customized_event_key_scoped_to_class
118
+ I18n.backend.store_translations(:en, {
119
+ :activerecord => {:state_machines => {:'foo' => {:events => {:park => 'stop'}}}}
120
+ })
121
+
122
+ machine = StateMachines::Machine.new(@model)
123
+ machine.event :park
124
+
125
+ assert_equal 'stop', machine.event(:park).human_name
126
+ end
127
+
128
+ def test_should_allow_customized_event_key_scoped_to_machine
129
+ I18n.backend.store_translations(:en, {
130
+ :activerecord => {:state_machines => {:state => {:events => {:park => 'stop'}}}}
131
+ })
132
+
133
+ machine = StateMachines::Machine.new(@model)
134
+ machine.event :park
135
+
136
+ assert_equal 'stop', machine.event(:park).human_name
137
+ end
138
+
139
+ def test_should_allow_customized_event_key_unscoped
140
+ I18n.backend.store_translations(:en, {
141
+ :activerecord => {:state_machines => {:events => {:park => 'stop'}}}
142
+ })
143
+
144
+ machine = StateMachines::Machine.new(@model)
145
+ machine.event :park
146
+
147
+ assert_equal 'stop', machine.event(:park).human_name
148
+ end
149
+
150
+ def test_should_only_add_locale_once_in_load_path
151
+ assert_equal 1, I18n.load_path.select { |path| path =~ %r{active_record/locale\.rb$} }.length
152
+
153
+ # Create another ActiveRecord model that will trigger the i18n feature
154
+ new_model
155
+
156
+ assert_equal 1, I18n.load_path.select { |path| path =~ %r{active_record/locale\.rb$} }.length
157
+ end
158
+
159
+ def test_should_prefer_other_locales_first
160
+ @original_load_path = I18n.load_path
161
+ I18n.backend = I18n::Backend::Simple.new
162
+ I18n.load_path = [File.dirname(__FILE__) + '/files/en.yml']
163
+
164
+ machine = StateMachines::Machine.new(@model)
165
+ machine.state :parked, :idling
166
+ machine.event :ignite
167
+
168
+ record = @model.new(:state => 'idling')
169
+
170
+ machine.invalidate(record, :state, :invalid_transition, [[:event, 'ignite']])
171
+ assert_equal ['State cannot transition'], record.errors.full_messages
172
+ ensure
173
+ I18n.load_path = @original_load_path
174
+ end
175
+
176
+ private
177
+ def interpolation_key(key)
178
+ "%{#{key}}"
179
+ end
180
+ end
@@ -0,0 +1,22 @@
1
+ require_relative 'test_helper'
2
+
3
+ class MachineWithLoopbackTest < BaseTestCase
4
+ def setup
5
+ @model = new_model do
6
+ connection.add_column table_name, :updated_at, :datetime
7
+ end
8
+
9
+ @machine = StateMachines::Machine.new(@model, :initial => :parked)
10
+ @machine.event :park
11
+
12
+ @record = @model.create(:updated_at => Time.now - 1)
13
+ @transition = StateMachines::Transition.new(@record, @machine, :park, :parked, :parked)
14
+
15
+ @timestamp = @record.updated_at
16
+ @transition.perform
17
+ end
18
+
19
+ def test_should_not_update_record
20
+ assert_equal @timestamp, @record.updated_at
21
+ end
22
+ end
@@ -0,0 +1,35 @@
1
+ require_relative 'test_helper'
2
+
3
+ class MachineWithNonColumnStateAttributeDefinedTest < BaseTestCase
4
+ def setup
5
+ @model = new_model do
6
+ def status=(value)
7
+ self['status'] = value
8
+ end
9
+
10
+ def status
11
+ self['status']
12
+ end
13
+ end
14
+
15
+ @machine = StateMachines::Machine.new(@model, :status, :initial => :parked)
16
+ @machine.other_states(:idling)
17
+ @record = @model.new
18
+ end
19
+
20
+ def test_should_return_false_for_predicate_if_does_not_match_current_value
21
+ refute @record.status?(:idling)
22
+ end
23
+
24
+ def test_should_return_true_for_predicate_if_matches_current_value
25
+ assert @record.status?(:parked)
26
+ end
27
+
28
+ def test_should_raise_exception_for_predicate_if_invalid_state_specified
29
+ assert_raise(IndexError) { @record.status?(:invalid) }
30
+ end
31
+
32
+ def test_should_set_initial_state_on_created_object
33
+ assert_equal 'parked', @record.status
34
+ end
35
+ end
@@ -0,0 +1,33 @@
1
+ require_relative 'test_helper'
2
+
3
+ class MachineWithNonColumnStateAttributeUndefinedTest < BaseTestCase
4
+ def setup
5
+ @model = new_model do
6
+ def initialize
7
+ # Skip attribute initialization
8
+ @initialized_state_machines = true
9
+ super
10
+ end
11
+ end
12
+
13
+ @machine = StateMachines::Machine.new(@model, :status, :initial => :parked)
14
+ @machine.other_states(:idling)
15
+ @record = @model.new
16
+ end
17
+
18
+ def test_should_not_define_a_column_for_the_attribute
19
+ assert_nil @model.columns_hash['status']
20
+ end
21
+
22
+ def test_should_define_a_reader_attribute_for_the_attribute
23
+ assert @record.respond_to?(:status)
24
+ end
25
+
26
+ def test_should_define_a_writer_attribute_for_the_attribute
27
+ assert @record.respond_to?(:status=)
28
+ end
29
+
30
+ def test_should_define_an_attribute_predicate
31
+ assert @record.respond_to?(:status?)
32
+ end
33
+ end
@@ -0,0 +1,26 @@
1
+ require_relative 'test_helper'
2
+
3
+ class MachineWithSameColumnDefaultTest < BaseTestCase
4
+ def setup
5
+ @original_stderr, $stderr = $stderr, StringIO.new
6
+
7
+ @model = new_model do
8
+ connection.add_column table_name, :status, :string, :default => 'parked'
9
+ end
10
+ @machine = StateMachines::Machine.new(@model, :status, :initial => :parked)
11
+ @record = @model.new
12
+ end
13
+
14
+ def test_should_use_machine_default
15
+ assert_equal 'parked', @record.status
16
+ end
17
+
18
+ def test_should_not_generate_a_warning
19
+ assert_no_match(/have defined a different default/, $stderr.string)
20
+ end
21
+
22
+ def teardown
23
+ $stderr = @original_stderr
24
+ super
25
+ end
26
+ end