state_machines-activemodel 0.0.2 → 0.0.3

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 (52) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/.travis.yml +14 -10
  4. data/Appraisals +4 -15
  5. data/Gemfile +3 -0
  6. data/LICENSE.txt +1 -1
  7. data/README.md +9 -6
  8. data/Rakefile +5 -6
  9. data/gemfiles/active_model_4.1.gemfile +5 -1
  10. data/gemfiles/active_model_4.2.gemfile +11 -0
  11. data/lib/state_machines/integrations/active_model.rb +105 -108
  12. data/lib/state_machines/integrations/active_model/locale.rb +8 -8
  13. data/lib/state_machines/integrations/{version.rb → active_model/version.rb} +1 -1
  14. data/state_machines-activemodel.gemspec +10 -9
  15. data/{spec/support → test/files}/en.yml +1 -1
  16. data/test/integration_test.rb +23 -0
  17. data/test/machine_by_default_test.rb +24 -0
  18. data/test/machine_errors_test.rb +19 -0
  19. data/test/machine_multiple_test.rb +18 -0
  20. data/test/machine_with_callbacks_test.rb +130 -0
  21. data/test/machine_with_dirty_attribute_and_custom_attributes_during_loopback_test.rb +26 -0
  22. data/test/machine_with_dirty_attribute_and_state_events_test.rb +23 -0
  23. data/test/machine_with_dirty_attributes_and_custom_attribute_test.rb +34 -0
  24. data/test/machine_with_dirty_attributes_during_loopback_test.rb +49 -0
  25. data/test/machine_with_dirty_attributes_test.rb +33 -0
  26. data/test/machine_with_dynamic_initial_state_test.rb +14 -0
  27. data/test/machine_with_events_test.rb +13 -0
  28. data/test/machine_with_failed_after_callbacks_test.rb +31 -0
  29. data/test/machine_with_failed_before_callbacks_test.rb +32 -0
  30. data/test/machine_with_initialized_state_test.rb +35 -0
  31. data/test/machine_with_internationalization_test.rb +190 -0
  32. data/test/machine_with_model_state_attribute_test.rb +31 -0
  33. data/test/machine_with_non_model_state_attribute_undefined_test.rb +26 -0
  34. data/test/machine_with_state_driven_validations_test.rb +31 -0
  35. data/test/machine_with_states_test.rb +13 -0
  36. data/test/machine_with_static_initial_state_test.rb +13 -0
  37. data/test/machine_with_validations_and_custom_attribute_test.rb +22 -0
  38. data/test/machine_with_validations_test.rb +46 -0
  39. data/test/test_helper.rb +58 -0
  40. metadata +83 -36
  41. data/gemfiles/active_model_3.2.gemfile +0 -7
  42. data/gemfiles/active_model_3.2.gemfile.lock +0 -50
  43. data/gemfiles/active_model_4.0.gemfile +0 -7
  44. data/gemfiles/active_model_4.0.gemfile.lock +0 -56
  45. data/gemfiles/active_model_4.1.gemfile.lock +0 -57
  46. data/gemfiles/active_model_edge.gemfile +0 -7
  47. data/gemfiles/active_model_edge.gemfile.lock +0 -62
  48. data/spec/active_model_spec.rb +0 -801
  49. data/spec/integration_spec.rb +0 -26
  50. data/spec/spec_helper.rb +0 -8
  51. data/spec/support/helpers.rb +0 -48
  52. data/spec/support/migration_helpers.rb +0 -43
@@ -0,0 +1,14 @@
1
+ require_relative 'test_helper'
2
+
3
+ class MachineWithDynamicInitialStateTest < BaseTestCase
4
+ def setup
5
+ @model = new_model
6
+ @machine = StateMachines::Machine.new(@model, initial: lambda { |_object| :parked }, integration: :active_model)
7
+ @machine.state :parked
8
+ end
9
+
10
+ def test_should_set_initial_state_on_created_object
11
+ record = @model.new
12
+ assert_equal 'parked', record.state
13
+ end
14
+ end
@@ -0,0 +1,13 @@
1
+ require_relative 'test_helper'
2
+
3
+ class MachineWithEventsTest < BaseTestCase
4
+ def setup
5
+ @model = new_model
6
+ @machine = StateMachines::Machine.new(@model)
7
+ @machine.event :shift_up
8
+ end
9
+
10
+ def test_should_humanize_name
11
+ assert_equal 'shift up', @machine.event(:shift_up).human_name
12
+ end
13
+ end
@@ -0,0 +1,31 @@
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, integration: :active_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_not_run_further_after_callbacks
29
+ assert_equal [:around_before, :around_after, :after_1], @callbacks
30
+ end
31
+ end
@@ -0,0 +1,32 @@
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, integration: :active_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
+ assert !@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_further_callbacks
30
+ assert_equal [:before_1], @callbacks
31
+ end
32
+ 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, integration: :active_model)
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,190 @@
1
+ require_relative 'test_helper'
2
+ require 'i18n'
3
+
4
+ class MachineWithInternationalizationTest < BaseTestCase
5
+ def setup
6
+ I18n.backend = I18n::Backend::Simple.new
7
+ # Initialize the backend
8
+ I18n.backend.translate(:en, 'activemodel.errors.messages.invalid_transition', event: 'ignite', value: 'idling')
9
+
10
+ @model = new_model { include ActiveModel::Validations }
11
+ end
12
+
13
+ def test_should_use_defaults
14
+ I18n.backend.store_translations(:en,
15
+ activemodel: { errors: { messages: { invalid_transition: 'cannot %{event}' } } }
16
+ )
17
+
18
+ machine = StateMachines::Machine.new(@model, action: :save)
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 ignite'], record.errors.full_messages
26
+ end
27
+
28
+ def test_should_allow_customized_error_key
29
+ I18n.backend.store_translations(:en,
30
+ activemodel: { errors: { messages: { bad_transition: 'cannot %{event}' } } }
31
+ )
32
+
33
+ machine = StateMachines::Machine.new(@model, action: :save, messages: { invalid_transition: :bad_transition })
34
+ machine.state :parked, :idling
35
+
36
+ record = @model.new
37
+ record.state = 'idling'
38
+
39
+ machine.invalidate(record, :state, :invalid_transition, [[:event, 'ignite']])
40
+ assert_equal ['State cannot ignite'], record.errors.full_messages
41
+ end
42
+
43
+ def test_should_allow_customized_error_string
44
+ machine = StateMachines::Machine.new(@model, action: :save, messages: { invalid_transition: 'cannot %{event}' })
45
+ machine.state :parked, :idling
46
+
47
+ record = @model.new(state: 'idling')
48
+
49
+ machine.invalidate(record, :state, :invalid_transition, [[:event, 'ignite']])
50
+ assert_equal ['State cannot ignite'], record.errors.full_messages
51
+ end
52
+
53
+ def test_should_allow_customized_state_key_scoped_to_class_and_machine
54
+ I18n.backend.store_translations(:en,
55
+ activemodel: { state_machines: { :'foo' => { state: { states: { parked: 'shutdown' } } } } }
56
+ )
57
+
58
+ machine = StateMachines::Machine.new(@model)
59
+ machine.state :parked
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
+ activemodel: { 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
+ activemodel: { 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
+ activemodel: { 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
+ activemodel: { 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
+ activemodel: { 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
+ activemodel: { 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
+ activemodel: { 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
+ activemodel: { 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_model/locale\.rb$} }.length
152
+
153
+ # Create another ActiveModel model that will triger the i18n feature
154
+ new_model
155
+
156
+ assert_equal 1, I18n.load_path.select { |path| path =~ %r{active_model/locale\.rb$} }.length
157
+ end
158
+
159
+ def test_should_add_locale_to_beginning_of_load_path
160
+ @original_load_path = I18n.load_path
161
+ I18n.backend = I18n::Backend::Simple.new
162
+
163
+ app_locale = File.dirname(__FILE__) + '/files/en.yml'
164
+ default_locale = File.dirname(__FILE__) + '/../lib/state_machines/integrations/active_model/locale.rb'
165
+ I18n.load_path = [app_locale]
166
+
167
+ StateMachines::Machine.new(@model)
168
+
169
+ assert_equal [default_locale, app_locale].map { |path| File.expand_path(path) }, I18n.load_path.map { |path| File.expand_path(path) }
170
+ ensure
171
+ I18n.load_path = @original_load_path
172
+ end
173
+
174
+ def test_should_prefer_other_locales_first
175
+ @original_load_path = I18n.load_path
176
+ I18n.backend = I18n::Backend::Simple.new
177
+ I18n.load_path = [File.dirname(__FILE__) + '/files/en.yml']
178
+
179
+ machine = StateMachines::Machine.new(@model)
180
+ machine.state :parked, :idling
181
+ machine.event :ignite
182
+
183
+ record = @model.new(state: 'idling')
184
+
185
+ machine.invalidate(record, :state, :invalid_transition, [[:event, 'ignite']])
186
+ assert_equal ['State cannot ignite'], record.errors.full_messages
187
+ ensure
188
+ I18n.load_path = @original_load_path
189
+ end
190
+ end
@@ -0,0 +1,31 @@
1
+ require_relative 'test_helper'
2
+
3
+ class MachineWithModelStateAttributeTest < BaseTestCase
4
+ def setup
5
+ @model = new_model
6
+ @machine = StateMachines::Machine.new(@model, initial: :parked, integration: :active_model)
7
+ @machine.other_states(:idling)
8
+
9
+ @record = @model.new
10
+ end
11
+
12
+ def test_should_have_an_attribute_predicate
13
+ assert @record.respond_to?(:state?)
14
+ end
15
+
16
+ def test_should_raise_exception_for_predicate_without_parameters
17
+ assert_raises(ArgumentError) { @record.state? }
18
+ end
19
+
20
+ def test_should_return_false_for_predicate_if_does_not_match_current_value
21
+ assert !@record.state?(:idling)
22
+ end
23
+
24
+ def test_should_return_true_for_predicate_if_matches_current_value
25
+ assert @record.state?(:parked)
26
+ end
27
+
28
+ def test_should_raise_exception_for_predicate_if_invalid_state_specified
29
+ assert_raises(IndexError) { @record.state?(:invalid) }
30
+ end
31
+ end
@@ -0,0 +1,26 @@
1
+ require_relative 'test_helper'
2
+
3
+ class MachineWithNonModelStateAttributeUndefinedTest < BaseTestCase
4
+ def setup
5
+ @model = new_model do
6
+ def initialize
7
+ end
8
+ end
9
+
10
+ @machine = StateMachines::Machine.new(@model, :status, initial: :parked, integration: :active_model)
11
+ @machine.other_states(:idling)
12
+ @record = @model.new
13
+ end
14
+
15
+ def test_should_not_define_a_reader_attribute_for_the_attribute
16
+ assert !@record.respond_to?(:status)
17
+ end
18
+
19
+ def test_should_not_define_a_writer_attribute_for_the_attribute
20
+ assert !@record.respond_to?(:status=)
21
+ end
22
+
23
+ def test_should_define_an_attribute_predicate
24
+ assert @record.respond_to?(:status?)
25
+ end
26
+ end
@@ -0,0 +1,31 @@
1
+ require_relative 'test_helper'
2
+
3
+ class MachineWithStateDrivenValidationsTest < BaseTestCase
4
+ def setup
5
+ @model = new_model do
6
+ include ActiveModel::Validations
7
+ attr_accessor :seatbelt
8
+ end
9
+
10
+ @machine = StateMachines::Machine.new(@model)
11
+ @machine.state :first_gear, :second_gear do
12
+ validates_presence_of :seatbelt
13
+ end
14
+ @machine.other_states :parked
15
+ end
16
+
17
+ def test_should_be_valid_if_validation_fails_outside_state_scope
18
+ record = @model.new(state: 'parked', seatbelt: nil)
19
+ assert record.valid?
20
+ end
21
+
22
+ def test_should_be_invalid_if_validation_fails_within_state_scope
23
+ record = @model.new(state: 'first_gear', seatbelt: nil)
24
+ assert !record.valid?
25
+ end
26
+
27
+ def test_should_be_valid_if_validation_succeeds_within_state_scope
28
+ record = @model.new(state: 'second_gear', seatbelt: true)
29
+ assert record.valid?
30
+ end
31
+ end
@@ -0,0 +1,13 @@
1
+ require_relative 'test_helper'
2
+
3
+ class MachineWithStatesTest < BaseTestCase
4
+ def setup
5
+ @model = new_model
6
+ @machine = StateMachines::Machine.new(@model)
7
+ @machine.state :first_gear
8
+ end
9
+
10
+ def test_should_humanize_name
11
+ assert_equal 'first gear', @machine.state(:first_gear).human_name
12
+ end
13
+ end
@@ -0,0 +1,13 @@
1
+ require_relative 'test_helper'
2
+
3
+ class MachineWithStaticInitialStateTest < BaseTestCase
4
+ def setup
5
+ @model = new_model
6
+ @machine = StateMachines::Machine.new(@model, initial: :parked, integration: :active_model)
7
+ end
8
+
9
+ def test_should_set_initial_state_on_created_object
10
+ record = @model.new
11
+ assert_equal 'parked', record.state
12
+ end
13
+ end
@@ -0,0 +1,22 @@
1
+ require_relative 'test_helper'
2
+
3
+ class MachineWithValidationsAndCustomAttributeTest < BaseTestCase
4
+ def setup
5
+ @model = new_model { include ActiveModel::Validations }
6
+
7
+ @machine = StateMachines::Machine.new(@model, :status, attribute: :state)
8
+ @machine.state :parked
9
+
10
+ @record = @model.new
11
+ end
12
+
13
+ def test_should_add_validation_errors_to_custom_attribute
14
+ @record.state = 'invalid'
15
+
16
+ assert !@record.valid?
17
+ assert_equal ['State is invalid'], @record.errors.full_messages
18
+
19
+ @record.state = 'parked'
20
+ assert @record.valid?
21
+ end
22
+ end