activeinteractor 0.1.7 → 1.0.0.beta.1

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