activeinteractor 1.0.0 → 1.0.5

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 (32) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +71 -1
  3. data/README.md +22 -7
  4. data/lib/active_interactor.rb +5 -1
  5. data/lib/active_interactor/config.rb +1 -1
  6. data/lib/active_interactor/context/attributes.rb +64 -15
  7. data/lib/active_interactor/context/base.rb +87 -0
  8. data/lib/active_interactor/context/errors.rb +47 -0
  9. data/lib/active_interactor/context/loader.rb +1 -1
  10. data/lib/active_interactor/context/status.rb +12 -19
  11. data/lib/active_interactor/interactor/context.rb +59 -3
  12. data/lib/active_interactor/interactor/perform.rb +5 -1
  13. data/lib/active_interactor/models.rb +13 -28
  14. data/lib/active_interactor/organizer/interactor_interface.rb +1 -1
  15. data/lib/active_interactor/organizer/interactor_interface_collection.rb +1 -0
  16. data/lib/active_interactor/rails/orm/dynamoid.rb +5 -0
  17. data/lib/active_interactor/rails/orm/mongoid.rb +5 -0
  18. data/lib/active_interactor/version.rb +1 -1
  19. data/lib/rails/generators/templates/organizer.erb +2 -2
  20. data/spec/active_interactor/base_spec.rb +33 -0
  21. data/spec/active_interactor/context/base_spec.rb +101 -12
  22. data/spec/integration/a_basic_interactor_spec.rb +48 -0
  23. data/spec/integration/a_basic_organizer_spec.rb +119 -0
  24. data/spec/integration/active_record_integration_spec.rb +8 -342
  25. data/spec/spec_helper.rb +18 -8
  26. data/spec/support/shared_examples/a_class_that_extends_active_interactor_models_example.rb +81 -0
  27. metadata +37 -42
  28. data/spec/support/coverage.rb +0 -4
  29. data/spec/support/coverage/reporters.rb +0 -11
  30. data/spec/support/coverage/reporters/codacy.rb +0 -39
  31. data/spec/support/coverage/reporters/simple_cov.rb +0 -54
  32. data/spec/support/coverage/runner.rb +0 -66
@@ -103,4 +103,52 @@ RSpec.describe 'A basic interactor', type: :integration do
103
103
  end
104
104
  end
105
105
  end
106
+
107
+ context 'having default context attributes {:foo => "foo"}' do
108
+ let(:interactor_class) do
109
+ build_interactor do
110
+ context_attribute :foo, default: -> { 'foo' }
111
+ end
112
+ end
113
+
114
+ describe '.perform' do
115
+ subject { interactor_class.perform(context_attributes) }
116
+
117
+ context 'when no context is passed' do
118
+ let(:context_attributes) { {} }
119
+
120
+ it { is_expected.to have_attributes(foo: 'foo') }
121
+ end
122
+
123
+ context 'when context {:foo => "bar"} is passed' do
124
+ let(:context_attributes) { { foo: 'bar' } }
125
+
126
+ it { is_expected.to have_attributes(foo: 'bar') }
127
+ end
128
+ end
129
+ end
130
+
131
+ context 'having default context attributes {:foo => "foo", :bar => "bar"} using the #context_attributes method' do
132
+ let(:interactor_class) do
133
+ build_interactor do
134
+ context_attributes foo: { default: -> { 'foo' } }, bar: { default: -> { 'bar' } }
135
+ end
136
+
137
+ describe '.perform' do
138
+ subject { interactor_class.perform(context_attributes) }
139
+
140
+ context 'when no context is passed' do
141
+ let(:context_attributes) { {} }
142
+
143
+ it { is_expected.to have_attributes(foo: 'foo', bar: 'bar') }
144
+ end
145
+
146
+ context 'when context {:foo => "bar"} is passed' do
147
+ let(:context_attributes) { { foo: 'bar' } }
148
+
149
+ it { is_expected.to have_attributes(foo: 'bar', bar: 'bar') }
150
+ end
151
+ end
152
+ end
153
+ end
106
154
  end
@@ -69,6 +69,26 @@ RSpec.describe 'A basic organizer', type: :integration do
69
69
  expect_any_instance_of(test_interactor_2).not_to receive(:perform!)
70
70
  subject
71
71
  end
72
+
73
+ # https://github.com/aaronmallen/activeinteractor/issues/169
74
+ context 'with error message "something went wrong"' do
75
+ let!(:test_interactor_1) do
76
+ build_interactor('TestInteractor1') do
77
+ def perform
78
+ context.fail!('something went wrong')
79
+ end
80
+ end
81
+ end
82
+
83
+ it { expect { subject }.not_to raise_error }
84
+ it { is_expected.to be_a interactor_class.context_class }
85
+ it { is_expected.to be_failure }
86
+ it { expect(subject.errors.count).to eq 1 }
87
+ it 'is expected to have errors "something went wrong" on :context' do
88
+ expect(subject.errors[:context]).not_to be_empty
89
+ expect(subject.errors[:context]).to include 'something went wrong'
90
+ end
91
+ end
72
92
  end
73
93
  end
74
94
 
@@ -92,6 +112,105 @@ RSpec.describe 'A basic organizer', type: :integration do
92
112
  expect_any_instance_of(test_interactor_1).to receive(:rollback)
93
113
  subject
94
114
  end
115
+
116
+ # https://github.com/aaronmallen/activeinteractor/issues/169
117
+ context 'with error message "something went wrong"' do
118
+ let!(:test_interactor_2) do
119
+ build_interactor('TestInteractor2') do
120
+ def perform
121
+ context.fail!('something went wrong')
122
+ end
123
+ end
124
+ end
125
+
126
+ it { expect { subject }.not_to raise_error }
127
+ it { is_expected.to be_a interactor_class.context_class }
128
+ it { is_expected.to be_failure }
129
+ it { expect(subject.errors.count).to eq 1 }
130
+ it 'is expected to have errors "something went wrong" on :context' do
131
+ expect(subject.errors[:context]).not_to be_empty
132
+ expect(subject.errors[:context]).to include 'something went wrong'
133
+ end
134
+ end
135
+ end
136
+ end
137
+
138
+ # https://github.com/aaronmallen/activeinteractor/issues/151
139
+ context 'with an interactor class having a predefined context class with attributes [:foo]' do
140
+ let!(:test_context_class) do
141
+ build_context('TestInteractor1Context') do
142
+ attributes :foo
143
+ end
144
+ end
145
+
146
+ # rubocop:disable Metrics/AbcSize
147
+ let!(:test_interactor_1) do
148
+ build_interactor('TestInteractor1') do
149
+ def perform
150
+ context.has_foo_as_method = context.foo.present?
151
+ context.has_foo_as_element = context[:foo].present?
152
+ context.has_bar_as_method = context.bar.present?
153
+ context.has_bar_as_element = context[:bar].present?
154
+ end
155
+ end
156
+ end
157
+ # rubocop:enable Metrics/AbcSize
158
+
159
+ let(:interactor_class) do
160
+ build_organizer do
161
+ organize :test_interactor_1
162
+ end
163
+ end
164
+
165
+ context 'when passing a context argument { :foo => "foo", :bar => "bar" }' do
166
+ let(:context_attributes) { { foo: 'foo', bar: 'bar' } }
167
+
168
+ describe '.perform' do
169
+ subject(:result) { interactor_class.perform(context_attributes) }
170
+ it { is_expected.to have_attributes(foo: 'foo', bar: 'bar') }
171
+
172
+ it 'is expected to copy all attributes in the contexts to each interactor' do
173
+ expect(subject.has_foo_as_method).to be true
174
+ expect(subject.has_foo_as_element).to be true
175
+ expect(subject.has_bar_as_method).to be true
176
+ expect(subject.has_bar_as_element).to be true
177
+ end
178
+
179
+ describe '#attributes' do
180
+ subject { result.attributes }
181
+
182
+ it { is_expected.to be_a Hash }
183
+ it { is_expected.to be_empty }
184
+ end
185
+ end
186
+
187
+ context 'with attributes [:foo] on the organizer context class' do
188
+ let!(:interactor_class) do
189
+ build_organizer do
190
+ context_attributes :foo
191
+ organize :test_interactor_1
192
+ end
193
+ end
194
+
195
+ describe '.perform' do
196
+ subject(:result) { interactor_class.perform(context_attributes) }
197
+ it { is_expected.to have_attributes(foo: 'foo', bar: 'bar') }
198
+
199
+ it 'is expected to copy all attributes in the contexts to each interactor' do
200
+ expect(subject.has_foo_as_method).to be true
201
+ expect(subject.has_foo_as_element).to be true
202
+ expect(subject.has_bar_as_method).to be true
203
+ expect(subject.has_bar_as_element).to be true
204
+ end
205
+
206
+ describe '#attributes' do
207
+ subject { result.attributes }
208
+
209
+ it { is_expected.to be_a Hash }
210
+ it { is_expected.to eq(foo: 'foo') }
211
+ end
212
+ end
213
+ end
95
214
  end
96
215
  end
97
216
  end
@@ -6,356 +6,22 @@ begin
6
6
  require 'active_interactor/rails/orm/active_record'
7
7
 
8
8
  RSpec.describe 'ActiveRecord integration', type: :integration do
9
- let!(:active_record_base_mock) do
10
- build_class('ActiveRecordBaseMock') do
11
- def self.attr_accessor(*attributes)
12
- attribute_keys.concat(attributes.map(&:to_sym))
13
- super
14
- end
15
-
16
- def self.attribute_keys
17
- @attribute_keys ||= []
18
- end
19
-
20
- def initialize(attributes = nil, _options = {})
21
- (attributes || {}).each do |key, value|
22
- instance_variable_set("@#{key}", value)
23
- end
24
- end
25
-
26
- def [](name)
27
- instance_variable_get("@#{name}")
28
- end
29
-
30
- def []=(name, value)
31
- instance_variable_set("@#{name}", value)
32
- end
33
-
34
- def attributes
35
- self.class.attribute_keys.each_with_object({}) do |key, hash|
36
- hash[key] = instance_variable_get("@#{key}")
37
- end
38
- end
39
-
40
- def to_h
41
- attributes.to_h
42
- end
43
- end
44
- end
9
+ let!(:active_record_base_mock) { build_class('ActiveRecordBaseMock') }
45
10
 
46
11
  context 'after ActiveSupport.run_load_hooks has been received with :active_record' do
47
12
  before { ActiveSupport.run_load_hooks(:active_record, active_record_base_mock) }
48
13
 
49
- describe 'an ActiveRecord model class with .acts_as_context' do
50
- let(:model_mock) do
51
- build_class('ModelMock', active_record_base_mock) do
52
- attr_accessor :foo
14
+ describe 'an ActiveRecord model class with .acts_as_context and attribute :foo' do
15
+ let(:model_class) do
16
+ build_class('ModelClass', active_record_base_mock) do
17
+ include ActiveModel::Attributes
18
+ include ActiveModel::Model
53
19
  acts_as_context
20
+ attribute :foo
54
21
  end
55
22
  end
56
23
 
57
- describe 'as an instance' do
58
- subject { model_mock.new }
59
-
60
- it { is_expected.to respond_to :called! }
61
- it { is_expected.to respond_to :fail! }
62
- it { is_expected.to respond_to :fail? }
63
- it { is_expected.to respond_to :failure? }
64
- it { is_expected.to respond_to :merge! }
65
- it { is_expected.to respond_to :rollback! }
66
- it { is_expected.to respond_to :success? }
67
- it { is_expected.to respond_to :successful? }
68
- end
69
-
70
- describe '.new' do
71
- subject { model_mock.new(attributes) }
72
- let(:attributes) { nil }
73
-
74
- it 'is expected not to receive #copy_flags!' do
75
- expect_any_instance_of(model_mock).not_to receive(:copy_flags!)
76
- subject
77
- end
78
-
79
- it 'is expected not to receive #copy_called!' do
80
- expect_any_instance_of(model_mock).not_to receive(:copy_called!)
81
- subject
82
- end
83
-
84
- it 'is expected to receive super on parent class with nil attributes' do
85
- expect(active_record_base_mock).to receive(:new).with(nil)
86
- subject
87
- end
88
-
89
- context 'with attributes { :foo => "foo" }' do
90
- let(:attributes) { { foo: 'foo' } }
91
-
92
- it { expect { subject }.not_to raise_error }
93
-
94
- it 'is expected to receive #copy_flags!' do
95
- expect_any_instance_of(model_mock).to receive(:copy_flags!).with(foo: 'foo')
96
- subject
97
- end
98
-
99
- it 'is expected to receive #copy_called!' do
100
- expect_any_instance_of(model_mock).to receive(:copy_called!).with(foo: 'foo')
101
- subject
102
- end
103
-
104
- it 'is expected to receive super on parent class with { :foo => "foo" }' do
105
- expect(active_record_base_mock).to receive(:new).with(foo: 'foo')
106
- subject
107
- end
108
- end
109
-
110
- context 'with attributes being an instance of ModelMock having attributes { :foo => "foo" }' do
111
- let(:previous_instance) { model_mock.new(foo: 'foo') }
112
- let(:attributes) { previous_instance }
113
-
114
- it { is_expected.to have_attributes(foo: 'foo') }
115
-
116
- context 'with _failed equal to true on previous instance' do
117
- before { previous_instance.instance_variable_set('@_failed', true) }
118
-
119
- it { is_expected.to be_failure }
120
- end
121
-
122
- context 'with _rolled_back equal to true on previous instance' do
123
- before { previous_instance.instance_variable_set('@_rolled_back', true) }
124
-
125
- it 'is expected to have instance variable @_rolled_back eq to true' do
126
- expect(subject.instance_variable_get('@_rolled_back')).to eq true
127
- end
128
- end
129
-
130
- context 'with _called eq to ["foo"] on previous instance' do
131
- before { previous_instance.instance_variable_set('@_called', %w[foo]) }
132
-
133
- it 'is expected to have instance variable @_called eq to ["foo"]' do
134
- expect(subject.instance_variable_get('@_called')).to eq %w[foo]
135
- end
136
- end
137
- end
138
- end
139
-
140
- describe '#merge!' do
141
- subject { model_mock.new(attributes).merge!(merge_instance) }
142
- context 'having attributes { :foo => nil }' do
143
- let(:attributes) { { foo: nil } }
144
-
145
- context 'with merging instance having attributes { :foo => "foo" }' do
146
- let(:merge_instance) { model_mock.new(foo: 'foo') }
147
-
148
- it { is_expected.to have_attributes(foo: 'foo') }
149
-
150
- context 'with _failed equal to true on merging instance' do
151
- before { merge_instance.instance_variable_set('@_failed', true) }
152
-
153
- it { is_expected.to be_failure }
154
- end
155
-
156
- context 'with _rolled_back equal to true on merging instance' do
157
- before { merge_instance.instance_variable_set('@_rolled_back', true) }
158
-
159
- it 'is expected to have instance variable @_rolled_back eq to true' do
160
- expect(subject.instance_variable_get('@_rolled_back')).to eq true
161
- end
162
- end
163
- end
164
- end
165
-
166
- context 'having attributes { :foo => "foo"}' do
167
- let(:attributes) { { foo: 'foo' } }
168
-
169
- context 'with merging instance having attributes { :foo => "bar" }' do
170
- let(:merge_instance) { model_mock.new(foo: 'bar') }
171
-
172
- it { is_expected.to have_attributes(foo: 'bar') }
173
-
174
- context 'with _failed equal to true on merging instance' do
175
- before { merge_instance.instance_variable_set('@_failed', true) }
176
-
177
- it { is_expected.to be_failure }
178
- end
179
-
180
- context 'with _rolled_back equal to true on merging instance' do
181
- before { merge_instance.instance_variable_set('@_rolled_back', true) }
182
-
183
- it 'is expected to have instance variable @_rolled_back eq to true' do
184
- expect(subject.instance_variable_get('@_rolled_back')).to eq true
185
- end
186
- end
187
- end
188
- end
189
- end
190
- end
191
-
192
- describe 'a basic interactor using an ActiveRecord model class as context' do
193
- let!(:model_mock) do
194
- require 'active_model'
195
- build_class('ModelMock', active_record_base_mock) do
196
- include ActiveModel::Validations
197
- attr_accessor :foo
198
- acts_as_context
199
- end
200
- end
201
-
202
- let(:interactor_class) do
203
- build_interactor do
204
- contextualize_with :model_mock
205
-
206
- def perform
207
- context.foo = 'foo'
208
- end
209
- end
210
- end
211
-
212
- include_examples 'a class with interactor methods'
213
- include_examples 'a class with interactor callback methods'
214
- include_examples 'a class with interactor context methods'
215
-
216
- describe '.context_class' do
217
- subject { interactor_class.context_class }
218
-
219
- it { is_expected.to eq model_mock }
220
- end
221
-
222
- describe '.perform' do
223
- subject { interactor_class.perform }
224
-
225
- it { is_expected.to be_a model_mock }
226
- it { is_expected.to be_successful }
227
- it { is_expected.to have_attributes(foo: 'foo') }
228
- end
229
-
230
- describe '.perform!' do
231
- subject { interactor_class.perform! }
232
-
233
- it { expect { subject }.not_to raise_error }
234
- it { is_expected.to be_a model_mock }
235
- it { is_expected.to be_successful }
236
- it { is_expected.to have_attributes(foo: 'foo') }
237
- end
238
-
239
- context 'failing the context on #perform' do
240
- let(:interactor_class) do
241
- build_interactor do
242
- contextualize_with :model_mock
243
-
244
- def perform
245
- context.fail!
246
- end
247
- end
248
- end
249
-
250
- describe '.perform' do
251
- subject { interactor_class.perform }
252
-
253
- it { expect { subject }.not_to raise_error }
254
- it { is_expected.to be_a model_mock }
255
- it { is_expected.to be_failure }
256
- it 'is expected to receive #rollback' do
257
- expect_any_instance_of(interactor_class).to receive(:rollback)
258
- subject
259
- end
260
- end
261
-
262
- describe '.perform!' do
263
- subject { interactor_class.perform! }
264
-
265
- it { expect { subject }.to raise_error(ActiveInteractor::Error::ContextFailure) }
266
- end
267
- end
268
- end
269
-
270
- describe 'a basic organizer using an ActiveRecord model class as context' do
271
- let!(:model_mock) do
272
- require 'active_model'
273
- build_class('ModelMock', active_record_base_mock) do
274
- include ActiveModel::Validations
275
- attr_accessor :first_name, :last_name
276
- acts_as_context
277
- end
278
- end
279
-
280
- context 'with each organized interactor using the model' do
281
- let!(:test_interactor_1) do
282
- build_interactor('TestInteractor1') do
283
- contextualize_with :model_mock
284
-
285
- def perform
286
- context.first_name = 'Test'
287
- end
288
- end
289
- end
290
-
291
- let!(:test_interactor_2) do
292
- build_interactor('TestInteractor2') do
293
- contextualize_with :model_mock
294
-
295
- def perform
296
- context.last_name = 'User'
297
- end
298
- end
299
- end
300
-
301
- let(:interactor_class) do
302
- build_organizer do
303
- contextualize_with :model_mock
304
- organize :test_interactor_1, :test_interactor_2
305
- end
306
- end
307
-
308
- include_examples 'a class with interactor methods'
309
- include_examples 'a class with interactor callback methods'
310
- include_examples 'a class with interactor context methods'
311
- include_examples 'a class with organizer callback methods'
312
-
313
- describe '.perform' do
314
- subject { interactor_class.perform }
315
-
316
- it { is_expected.to be_a model_mock }
317
- it { is_expected.to be_successful }
318
- it { is_expected.to have_attributes(first_name: 'Test', last_name: 'User') }
319
- end
320
- end
321
-
322
- context 'with each organized interactor using their default context' do
323
- let!(:test_interactor_1) do
324
- build_interactor('TestInteractor1') do
325
- def perform
326
- context.first_name = 'Test'
327
- end
328
- end
329
- end
330
-
331
- let!(:test_interactor_2) do
332
- build_interactor('TestInteractor2') do
333
- def perform
334
- context.last_name = 'User'
335
- end
336
- end
337
- end
338
-
339
- let(:interactor_class) do
340
- build_organizer do
341
- contextualize_with :model_mock
342
- organize :test_interactor_1, :test_interactor_2
343
- end
344
- end
345
-
346
- include_examples 'a class with interactor methods'
347
- include_examples 'a class with interactor callback methods'
348
- include_examples 'a class with interactor context methods'
349
- include_examples 'a class with organizer callback methods'
350
-
351
- describe '.perform' do
352
- subject { interactor_class.perform }
353
-
354
- it { is_expected.to be_a model_mock }
355
- it { is_expected.to be_successful }
356
- it { is_expected.to have_attributes(first_name: 'Test', last_name: 'User') }
357
- end
358
- end
24
+ include_examples 'A class that extends ActiveInteractor::Models'
359
25
  end
360
26
  end
361
27
  end