activeinteractor 0.1.7 → 1.0.0.beta.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 (58) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +36 -1
  3. data/README.md +397 -395
  4. data/lib/active_interactor.rb +12 -30
  5. data/lib/active_interactor/base.rb +18 -8
  6. data/lib/active_interactor/context.rb +4 -181
  7. data/lib/active_interactor/context/attributes.rb +37 -149
  8. data/lib/active_interactor/context/base.rb +141 -0
  9. data/lib/active_interactor/context/loader.rb +45 -0
  10. data/lib/active_interactor/error.rb +22 -15
  11. data/lib/active_interactor/interactor.rb +24 -57
  12. data/lib/active_interactor/interactor/callbacks.rb +64 -76
  13. data/lib/active_interactor/interactor/context.rb +97 -63
  14. data/lib/active_interactor/interactor/worker.rb +22 -65
  15. data/lib/active_interactor/organizer.rb +180 -164
  16. data/lib/active_interactor/version.rb +2 -3
  17. data/lib/rails/generators/active_interactor.rb +2 -37
  18. data/lib/rails/generators/active_interactor/application_interactor_generator.rb +23 -0
  19. data/lib/rails/generators/active_interactor/install_generator.rb +8 -12
  20. data/lib/rails/generators/active_interactor/templates/application_context.rb +4 -0
  21. data/lib/rails/generators/{templates/application_interactor.erb → active_interactor/templates/application_interactor.rb} +0 -0
  22. data/lib/rails/generators/active_interactor/templates/application_organizer.rb +4 -0
  23. data/lib/rails/generators/active_interactor/templates/initializer.erb +5 -0
  24. data/lib/rails/generators/interactor/context/rspec_generator.rb +19 -0
  25. data/lib/rails/generators/interactor/context/templates/rspec.erb +7 -0
  26. data/lib/rails/generators/interactor/context/templates/test_unit.erb +9 -0
  27. data/lib/rails/generators/interactor/context/test_unit_generator.rb +19 -0
  28. data/lib/rails/generators/interactor/context_generator.rb +19 -0
  29. data/lib/rails/generators/interactor/interactor_generator.rb +8 -3
  30. data/lib/rails/generators/interactor/organizer_generator.rb +8 -3
  31. data/lib/rails/generators/interactor/rspec_generator.rb +4 -3
  32. data/lib/rails/generators/interactor/templates/context.erb +4 -0
  33. data/lib/rails/generators/{templates → interactor/templates}/interactor.erb +0 -0
  34. data/lib/rails/generators/{templates → interactor/templates}/organizer.erb +1 -1
  35. data/lib/rails/generators/{templates → interactor/templates}/rspec.erb +0 -0
  36. data/lib/rails/generators/{templates → interactor/templates}/test_unit.erb +0 -0
  37. data/lib/rails/generators/interactor/test_unit_generator.rb +4 -3
  38. data/spec/active_interactor/base_spec.rb +51 -0
  39. data/spec/active_interactor/context/base_spec.rb +229 -0
  40. data/spec/active_interactor/error_spec.rb +43 -0
  41. data/spec/active_interactor/interactor/worker_spec.rb +89 -0
  42. data/spec/active_interactor/organizer_spec.rb +178 -0
  43. data/spec/active_interactor_spec.rb +26 -0
  44. data/spec/integration/basic_callback_integration_spec.rb +355 -0
  45. data/spec/integration/basic_context_integration_spec.rb +73 -0
  46. data/spec/integration/basic_integration_spec.rb +220 -0
  47. data/spec/integration/basic_validations_integration_spec.rb +204 -0
  48. data/spec/spec_helper.rb +44 -0
  49. data/spec/support/helpers/factories.rb +41 -0
  50. data/spec/support/shared_examples/a_class_with_interactor_callback_methods_example.rb +99 -0
  51. data/spec/support/shared_examples/a_class_with_interactor_context_methods_example.rb +58 -0
  52. data/spec/support/shared_examples/a_class_with_interactor_methods_example.rb +21 -0
  53. data/spec/support/shared_examples/a_class_with_organizer_callback_methods_example.rb +39 -0
  54. data/spec/support/spec_helpers.rb +7 -0
  55. metadata +68 -138
  56. data/lib/active_interactor/configuration.rb +0 -38
  57. data/lib/active_interactor/interactor/execution.rb +0 -24
  58. data/lib/rails/generators/templates/initializer.erb +0 -15
@@ -0,0 +1,73 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ RSpec.describe 'Basic Context Integration', type: :integration do
6
+ describe 'An interactor with context attributes' do
7
+ let(:interactor_class) do
8
+ build_interactor do
9
+ context_attributes :test_field
10
+
11
+ def perform
12
+ context.test_field = 'test'
13
+ context.some_other_field = 'test 2'
14
+ end
15
+ end
16
+ end
17
+
18
+ include_examples 'a class with interactor methods'
19
+ include_examples 'a class with interactor callback methods'
20
+ include_examples 'a class with interactor context methods'
21
+
22
+ describe '.perform' do
23
+ subject(:result) { interactor_class.perform }
24
+
25
+ it { is_expected.to be_a interactor_class.context_class }
26
+ it { is_expected.to be_successful }
27
+ it { is_expected.to have_attributes(test_field: 'test', some_other_field: 'test 2') }
28
+
29
+ describe '.attributes' do
30
+ subject { result.attributes }
31
+
32
+ it { is_expected.to eq(test_field: 'test') }
33
+ end
34
+ end
35
+ end
36
+
37
+ describe 'An interactor with a defined context class named "AnInteractor"' do
38
+ let(:interactor_class) { build_interactor('AnInteractor') }
39
+ let!(:context_class) { build_context('AnInteractorContext') }
40
+
41
+ include_examples 'a class with interactor methods'
42
+ include_examples 'a class with interactor callback methods'
43
+ include_examples 'a class with interactor context methods'
44
+
45
+ describe '.context_class' do
46
+ subject { interactor_class.context_class }
47
+
48
+ it { is_expected.to eq AnInteractorContext }
49
+ it { is_expected.to be < ActiveInteractor::Context::Base }
50
+ end
51
+ end
52
+
53
+ describe 'An interactor contextualized with ATestContext' do
54
+ let(:interactor_class) do
55
+ build_interactor do
56
+ contextualize_with :a_test_context
57
+ end
58
+ end
59
+
60
+ let!(:context_class) { build_context('ATestContext') }
61
+
62
+ include_examples 'a class with interactor methods'
63
+ include_examples 'a class with interactor callback methods'
64
+ include_examples 'a class with interactor context methods'
65
+
66
+ describe '.context_class' do
67
+ subject { interactor_class.context_class }
68
+
69
+ it { is_expected.to eq ATestContext }
70
+ it { is_expected.to be < ActiveInteractor::Context::Base }
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,220 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ RSpec.describe 'Basic Integration', type: :integration do
6
+ describe 'A basic interactor' do
7
+ let(:interactor_class) do
8
+ build_interactor do
9
+ def perform
10
+ context.test_field = 'test'
11
+ end
12
+ end
13
+ end
14
+
15
+ include_examples 'a class with interactor methods'
16
+ include_examples 'a class with interactor callback methods'
17
+ include_examples 'a class with interactor context methods'
18
+
19
+ describe '.context_class' do
20
+ subject { interactor_class.context_class }
21
+
22
+ it { is_expected.to eq TestInteractor::Context }
23
+ it { is_expected.to be < ActiveInteractor::Context::Base }
24
+ end
25
+
26
+ describe '.perform' do
27
+ subject { interactor_class.perform }
28
+
29
+ it { is_expected.to be_a interactor_class.context_class }
30
+ it { is_expected.to be_successful }
31
+ it { is_expected.to have_attributes(test_field: 'test') }
32
+ end
33
+
34
+ describe '.perform!' do
35
+ subject { interactor_class.perform! }
36
+
37
+ it { expect { subject }.not_to raise_error }
38
+ it { is_expected.to be_a interactor_class.context_class }
39
+ it { is_expected.to be_successful }
40
+ it { is_expected.to have_attributes(test_field: 'test') }
41
+ end
42
+ end
43
+
44
+ describe 'A failing interactor' do
45
+ let(:interactor_class) do
46
+ build_interactor do
47
+ def perform
48
+ context.fail!
49
+ end
50
+ end
51
+ end
52
+
53
+ include_examples 'a class with interactor methods'
54
+ include_examples 'a class with interactor callback methods'
55
+ include_examples 'a class with interactor context methods'
56
+
57
+ describe '.context_class' do
58
+ subject { interactor_class.context_class }
59
+
60
+ it { is_expected.to eq TestInteractor::Context }
61
+ it { is_expected.to be < ActiveInteractor::Context::Base }
62
+ end
63
+
64
+ describe '.perform' do
65
+ subject { interactor_class.perform }
66
+
67
+ it { expect { subject }.not_to raise_error }
68
+ it { is_expected.to be_a interactor_class.context_class }
69
+ it { is_expected.to be_failure }
70
+ it 'is expected to #rollback' do
71
+ expect_any_instance_of(interactor_class).to receive(:rollback)
72
+ subject
73
+ end
74
+ end
75
+
76
+ describe '.perform!' do
77
+ subject { interactor_class.perform! }
78
+
79
+ it { expect { subject }.to raise_error(ActiveInteractor::Error::ContextFailure) }
80
+ end
81
+ end
82
+
83
+ describe 'A basic organizer' do
84
+ let!(:test_interactor_1) do
85
+ build_interactor('TestInteractor1') do
86
+ def perform
87
+ context.test_field_1 = 'test 1'
88
+ end
89
+ end
90
+ end
91
+
92
+ let!(:test_interactor_2) do
93
+ build_interactor('TestInteractor2') do
94
+ def perform
95
+ context.test_field_2 = 'test 2'
96
+ end
97
+ end
98
+ end
99
+
100
+ let(:interactor_class) do
101
+ build_organizer do
102
+ organize TestInteractor1, TestInteractor2
103
+ end
104
+ end
105
+
106
+ include_examples 'a class with interactor methods'
107
+ include_examples 'a class with interactor callback methods'
108
+ include_examples 'a class with interactor context methods'
109
+ include_examples 'a class with organizer callback methods'
110
+
111
+ describe '.context_class' do
112
+ subject { interactor_class.context_class }
113
+
114
+ it { is_expected.to eq TestOrganizer::Context }
115
+ it { is_expected.to be < ActiveInteractor::Context::Base }
116
+ end
117
+
118
+ describe '.organized' do
119
+ subject { interactor_class.organized }
120
+
121
+ it { is_expected.to eq [TestInteractor1, TestInteractor2] }
122
+ end
123
+
124
+ describe '.perform' do
125
+ subject { interactor_class.perform }
126
+
127
+ it { is_expected.to be_a interactor_class.context_class }
128
+ it { is_expected.to be_successful }
129
+ it { is_expected.to have_attributes(test_field_1: 'test 1', test_field_2: 'test 2') }
130
+ end
131
+ end
132
+
133
+ describe 'A basic organizer having the first interactor fail' do
134
+ let!(:test_interactor_1) do
135
+ build_interactor('TestInteractor1') do
136
+ def perform
137
+ context.fail!
138
+ end
139
+ end
140
+ end
141
+
142
+ let!(:test_interactor_2) do
143
+ build_interactor('TestInteractor2') do
144
+ def perform
145
+ context.test_field_2 = 'test 2'
146
+ end
147
+ end
148
+ end
149
+
150
+ let(:interactor_class) do
151
+ build_organizer do
152
+ organize TestInteractor1, TestInteractor2
153
+ end
154
+ end
155
+
156
+ include_examples 'a class with interactor methods'
157
+ include_examples 'a class with interactor callback methods'
158
+ include_examples 'a class with interactor context methods'
159
+ include_examples 'a class with organizer callback methods'
160
+
161
+ describe '.perform' do
162
+ subject { interactor_class.perform }
163
+
164
+ it { expect { subject }.not_to raise_error }
165
+ it { is_expected.to be_a interactor_class.context_class }
166
+ it { is_expected.to be_failure }
167
+ it 'is expected to rollback the first interactor' do
168
+ expect_any_instance_of(test_interactor_1).to receive(:rollback)
169
+ .and_call_original
170
+ subject
171
+ end
172
+ it 'is expected not to call #perform! on the second interactor' do
173
+ expect_any_instance_of(test_interactor_2).not_to receive(:perform!)
174
+ subject
175
+ end
176
+ end
177
+ end
178
+
179
+ describe 'A basic organizer having the second interactor fail' do
180
+ let!(:test_interactor_1) do
181
+ build_interactor('TestInteractor1') do
182
+ def perform
183
+ context.test_field_1 = 'test 1'
184
+ end
185
+ end
186
+ end
187
+
188
+ let!(:test_interactor_2) do
189
+ build_interactor('TestInteractor2') do
190
+ def perform
191
+ context.fail!
192
+ end
193
+ end
194
+ end
195
+
196
+ let(:interactor_class) do
197
+ build_organizer do
198
+ organize TestInteractor1, TestInteractor2
199
+ end
200
+ end
201
+
202
+ include_examples 'a class with interactor methods'
203
+ include_examples 'a class with interactor callback methods'
204
+ include_examples 'a class with interactor context methods'
205
+ include_examples 'a class with organizer callback methods'
206
+
207
+ describe '.perform' do
208
+ subject { interactor_class.perform }
209
+
210
+ it { expect { subject }.not_to raise_error }
211
+ it { is_expected.to be_a interactor_class.context_class }
212
+ it { is_expected.to be_failure }
213
+ it 'is expected to rollback all interactors' do
214
+ expect_any_instance_of(test_interactor_2).to receive(:rollback)
215
+ expect_any_instance_of(test_interactor_1).to receive(:rollback)
216
+ subject
217
+ end
218
+ end
219
+ end
220
+ end
@@ -0,0 +1,204 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ RSpec.describe 'Basic Validations Integration', type: :integration do
6
+ describe 'An interactor with validations' do
7
+ let(:interactor_class) do
8
+ build_interactor do
9
+ context_validates :test_field, presence: true
10
+
11
+ def perform
12
+ context.other_field = context.test_field
13
+ end
14
+
15
+ def rollback
16
+ context.other_field = 'failed'
17
+ end
18
+ end
19
+ end
20
+
21
+ include_examples 'a class with interactor methods'
22
+ include_examples 'a class with interactor callback methods'
23
+ include_examples 'a class with interactor context methods'
24
+
25
+ describe '.perform' do
26
+ subject { interactor_class.perform(context_attributes) }
27
+
28
+ context 'with valid context attributes' do
29
+ let(:context_attributes) { { test_field: 'test' } }
30
+
31
+ it { is_expected.to be_a interactor_class.context_class }
32
+ it { is_expected.to be_successful }
33
+ it { is_expected.to have_attributes(test_field: 'test', other_field: 'test') }
34
+ end
35
+
36
+ context 'with invalid context attributes' do
37
+ let(:context_attributes) { {} }
38
+
39
+ it { is_expected.to be_a interactor_class.context_class }
40
+ it { is_expected.to be_failure }
41
+ it { is_expected.to have_attributes(other_field: 'failed') }
42
+ it 'is expected to have errors on :test_field' do
43
+ expect(subject.errors[:test_field]).not_to be_nil
44
+ end
45
+ end
46
+ end
47
+ end
48
+
49
+ describe 'An interactor with conditional validations' do
50
+ let(:interactor_class) do
51
+ build_interactor do
52
+ context_validates :test_field, presence: true, if: :test_condition
53
+
54
+ def perform
55
+ context.other_field = context.test_field
56
+ end
57
+
58
+ def rollback
59
+ context.other_field = 'failed'
60
+ end
61
+ end
62
+ end
63
+
64
+ include_examples 'a class with interactor methods'
65
+ include_examples 'a class with interactor callback methods'
66
+ include_examples 'a class with interactor context methods'
67
+
68
+ describe '.perform' do
69
+ subject { interactor_class.perform(context_attributes) }
70
+
71
+ context 'with :test_field defined and :test_condition false' do
72
+ let(:context_attributes) { { test_field: 'test', test_condition: false } }
73
+
74
+ it { is_expected.to be_a interactor_class.context_class }
75
+ it { is_expected.to be_successful }
76
+ it { is_expected.to have_attributes(test_field: 'test', test_condition: false, other_field: 'test') }
77
+ end
78
+
79
+ context 'with :test_field defined and :test_condition true' do
80
+ let(:context_attributes) { { test_field: 'test', test_condition: true } }
81
+
82
+ it { is_expected.to be_a interactor_class.context_class }
83
+ it { is_expected.to be_successful }
84
+ it { is_expected.to have_attributes(test_field: 'test', test_condition: true, other_field: 'test') }
85
+ end
86
+
87
+ context 'with :test_field undefined and :test_condition true' do
88
+ let(:context_attributes) { { test_condition: true } }
89
+
90
+ it { is_expected.to be_a interactor_class.context_class }
91
+ it { is_expected.to be_failure }
92
+ it { is_expected.to have_attributes(test_condition: true, other_field: 'failed') }
93
+ it 'is expected to have errors on :test_field' do
94
+ expect(subject.errors[:test_field]).not_to be_nil
95
+ end
96
+ end
97
+ end
98
+ end
99
+
100
+ describe 'An interactor named "AnInteractor" with validations on context class named "AnInteractorContext"' do
101
+ let!(:context_class) do
102
+ build_context('AnInteractorContext') do
103
+ validates :test_field, presence: true
104
+ end
105
+ end
106
+ let(:interactor_class) { build_interactor('AnInteractor') }
107
+
108
+ include_examples 'a class with interactor methods'
109
+ include_examples 'a class with interactor callback methods'
110
+ include_examples 'a class with interactor context methods'
111
+
112
+ describe '.context_class' do
113
+ subject { interactor_class.context_class }
114
+
115
+ it { is_expected.to eq AnInteractorContext }
116
+ it { is_expected.to be < ActiveInteractor::Context::Base }
117
+ end
118
+
119
+ subject { interactor_class.perform(context_attributes) }
120
+
121
+ context 'with valid context attributes' do
122
+ let(:context_attributes) { { test_field: 'test' } }
123
+
124
+ it { is_expected.to be_an AnInteractorContext }
125
+ it { is_expected.to be_successful }
126
+ end
127
+
128
+ context 'with invalid context attributes' do
129
+ let(:context_attributes) { {} }
130
+
131
+ it { is_expected.to be_an AnInteractorContext }
132
+ it { is_expected.to be_failure }
133
+ it 'is expected to have errors on :some_field' do
134
+ expect(subject.errors[:test_field]).not_to be_nil
135
+ end
136
+ end
137
+ end
138
+
139
+ describe 'An interactor with validations having validation context on :calling' do
140
+ let(:interactor_class) do
141
+ build_interactor do
142
+ context_validates :test_field, presence: true, on: :calling
143
+ end
144
+ end
145
+
146
+ include_examples 'a class with interactor methods'
147
+ include_examples 'a class with interactor callback methods'
148
+ include_examples 'a class with interactor context methods'
149
+
150
+ subject { interactor_class.perform(context_attributes) }
151
+
152
+ context 'with :test_field "test"' do
153
+ let(:context_attributes) { { test_field: 'test' } }
154
+
155
+ it { is_expected.to be_an TestInteractor::Context }
156
+ it { is_expected.to be_successful }
157
+ end
158
+
159
+ context 'with :test_field nil' do
160
+ let(:context_attributes) { {} }
161
+
162
+ it { is_expected.to be_an TestInteractor::Context }
163
+ it { is_expected.to be_failure }
164
+ it 'is expected to have errors on :some_field' do
165
+ expect(subject.errors[:test_field]).not_to be_nil
166
+ end
167
+ end
168
+ end
169
+
170
+ describe 'An interactor with validations having validation context on :called' do
171
+ let(:interactor_class) do
172
+ build_interactor do
173
+ context_validates :test_field, presence: true, on: :called
174
+
175
+ def perform
176
+ context.test_field = 'test' if context.should_have_test_field
177
+ end
178
+ end
179
+ end
180
+
181
+ include_examples 'a class with interactor methods'
182
+ include_examples 'a class with interactor callback methods'
183
+ include_examples 'a class with interactor context methods'
184
+
185
+ subject { interactor_class.perform(context_attributes) }
186
+
187
+ context 'with :should_have_test_field true' do
188
+ let(:context_attributes) { { should_have_test_field: true } }
189
+
190
+ it { is_expected.to be_an TestInteractor::Context }
191
+ it { is_expected.to be_successful }
192
+ end
193
+
194
+ context 'with :should_have_test_field false' do
195
+ let(:context_attributes) { { should_have_test_field: false } }
196
+
197
+ it { is_expected.to be_an TestInteractor::Context }
198
+ it { is_expected.to be_failure }
199
+ it 'is expected to have errors on :some_field' do
200
+ expect(subject.errors[:test_field]).not_to be_nil
201
+ end
202
+ end
203
+ end
204
+ end