state_machines-activemodel 0.0.2 → 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
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