state_machines-activerecord 0.0.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 (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