activeinteractor 1.0.1 → 1.1.0
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +59 -1
- data/README.md +20 -5
- data/lib/active_interactor.rb +1 -0
- data/lib/active_interactor/config.rb +1 -1
- data/lib/active_interactor/context/attributes.rb +56 -11
- data/lib/active_interactor/context/base.rb +1 -0
- data/lib/active_interactor/context/errors.rb +47 -0
- data/lib/active_interactor/context/loader.rb +1 -1
- data/lib/active_interactor/context/status.rb +12 -19
- data/lib/active_interactor/interactor/context.rb +2 -1
- data/lib/active_interactor/interactor/perform.rb +5 -1
- data/lib/active_interactor/models.rb +13 -28
- data/lib/active_interactor/organizer/interactor_interface.rb +25 -8
- data/lib/active_interactor/organizer/interactor_interface_collection.rb +1 -0
- data/lib/active_interactor/organizer/perform.rb +11 -2
- data/lib/active_interactor/rails/orm/dynamoid.rb +5 -0
- data/lib/active_interactor/rails/orm/mongoid.rb +5 -0
- data/lib/active_interactor/version.rb +1 -1
- data/lib/rails/generators/templates/organizer.erb +2 -2
- data/spec/active_interactor/base_spec.rb +33 -0
- data/spec/active_interactor/context/base_spec.rb +44 -1
- data/spec/active_interactor/organizer/interactor_interface_spec.rb +74 -2
- data/spec/integration/a_basic_organizer_spec.rb +53 -1
- data/spec/integration/active_record_integration_spec.rb +8 -342
- data/spec/integration/an_organizer_with_failing_nested_organizer_spec.rb +47 -0
- data/spec/integration/an_organizer_with_options_callbacks_spec.rb +63 -0
- data/spec/spec_helper.rb +18 -8
- data/spec/support/shared_examples/a_class_that_extends_active_interactor_models_example.rb +81 -0
- metadata +40 -41
- data/spec/support/coverage.rb +0 -4
- data/spec/support/coverage/reporters.rb +0 -11
- data/spec/support/coverage/reporters/codacy.rb +0 -39
- data/spec/support/coverage/reporters/simple_cov.rb +0 -54
- data/spec/support/coverage/runner.rb +0 -66
@@ -14,6 +14,13 @@ module ActiveInteractor
|
|
14
14
|
#
|
15
15
|
# @return [Hash{Symbol=>Proc, Symbol}] conditional options for the {ActiveInteractor::Base interactor} class
|
16
16
|
#
|
17
|
+
# @!attribute [r] callbacks
|
18
|
+
# Callbacks for the interactor_class
|
19
|
+
#
|
20
|
+
# @since 1.1.0
|
21
|
+
#
|
22
|
+
# @return [Hash{Symbol=>*}] the interactor callbacks
|
23
|
+
#
|
17
24
|
# @!attribute [r] interactor_class
|
18
25
|
# An {ActiveInteractor::Base interactor} class
|
19
26
|
#
|
@@ -27,11 +34,12 @@ module ActiveInteractor
|
|
27
34
|
# @return [Hash{Symbol=>*}] {Interactor::Perform::Options} for the {ActiveInteractor::Base interactor}
|
28
35
|
# {Interactor::Perform#perform #perform}
|
29
36
|
class InteractorInterface
|
30
|
-
attr_reader :filters, :interactor_class, :perform_options
|
37
|
+
attr_reader :filters, :callbacks, :interactor_class, :perform_options
|
31
38
|
|
32
39
|
# Keywords for conditional filters
|
33
40
|
# @return [Array<Symbol>]
|
34
41
|
CONDITIONAL_FILTERS = %i[if unless].freeze
|
42
|
+
CALLBACKS = %i[before after].freeze
|
35
43
|
|
36
44
|
# Initialize a new instance of {InteractorInterface}
|
37
45
|
#
|
@@ -40,9 +48,10 @@ module ActiveInteractor
|
|
40
48
|
# {Interactor::Perform::ClassMethods#perform .perform}. See {Interactor::Perform::Options}.
|
41
49
|
# @return [InteractorInterface] a new instance of {InteractorInterface}
|
42
50
|
def initialize(interactor_class, options = {})
|
43
|
-
@interactor_class = interactor_class.to_s.
|
51
|
+
@interactor_class = interactor_class.to_s.camelize.safe_constantize
|
44
52
|
@filters = options.select { |key, _value| CONDITIONAL_FILTERS.include?(key) }
|
45
|
-
@
|
53
|
+
@callbacks = options.select { |key, _value| CALLBACKS.include?(key) }
|
54
|
+
@perform_options = options.reject { |key, _value| CONDITIONAL_FILTERS.include?(key) || CALLBACKS.include?(key) }
|
46
55
|
end
|
47
56
|
|
48
57
|
# Call the {#interactor_class} {Interactor::Perform::ClassMethods#perform .perform} or
|
@@ -57,21 +66,29 @@ module ActiveInteractor
|
|
57
66
|
# {Context::Status#fail! fails} its {Context::Base context}.
|
58
67
|
# @return [Class] an instance of {Context::Base context}
|
59
68
|
def perform(target, context, fail_on_error = false, perform_options = {})
|
60
|
-
return if check_conditionals(target,
|
61
|
-
return if check_conditionals(target,
|
69
|
+
return if check_conditionals(target, :if) == false
|
70
|
+
return if check_conditionals(target, :unless) == true
|
62
71
|
|
63
72
|
method = fail_on_error ? :perform! : :perform
|
64
73
|
options = self.perform_options.merge(perform_options)
|
65
74
|
interactor_class.send(method, context, options)
|
66
75
|
end
|
67
76
|
|
77
|
+
def execute_inplace_callback(target, callback)
|
78
|
+
resolve_option(target, callbacks[callback])
|
79
|
+
end
|
80
|
+
|
68
81
|
private
|
69
82
|
|
70
83
|
def check_conditionals(target, filter)
|
71
|
-
|
84
|
+
resolve_option(target, filters[filter])
|
85
|
+
end
|
86
|
+
|
87
|
+
def resolve_option(target, opt)
|
88
|
+
return unless opt
|
72
89
|
|
73
|
-
return target.send(
|
74
|
-
return target.instance_exec(&
|
90
|
+
return target.send(opt) if opt.is_a?(Symbol)
|
91
|
+
return target.instance_exec(&opt) if opt.is_a?(Proc)
|
75
92
|
end
|
76
93
|
end
|
77
94
|
end
|
@@ -14,6 +14,7 @@ module ActiveInteractor
|
|
14
14
|
# @return [Array<InteractorInterface>] the {InteractorInterface} collection
|
15
15
|
class InteractorInterfaceCollection
|
16
16
|
attr_reader :collection
|
17
|
+
|
17
18
|
# @!method map(&block)
|
18
19
|
# Invokes the given block once for each element of {#collection}.
|
19
20
|
# @return [Array] a new array containing the values returned by the block.
|
@@ -73,10 +73,19 @@ module ActiveInteractor
|
|
73
73
|
context_fail! if contexts.any?(&:failure?)
|
74
74
|
end
|
75
75
|
|
76
|
+
def execute_and_merge_contexts(interface)
|
77
|
+
interface.execute_inplace_callback(self, :before)
|
78
|
+
result = execute_interactor_with_callbacks(interface, true)
|
79
|
+
return if result.nil?
|
80
|
+
|
81
|
+
context.merge!(result)
|
82
|
+
context_fail! if result.failure?
|
83
|
+
interface.execute_inplace_callback(self, :after)
|
84
|
+
end
|
85
|
+
|
76
86
|
def perform_in_order
|
77
87
|
self.class.organized.each do |interface|
|
78
|
-
|
79
|
-
context.merge!(result) if result
|
88
|
+
execute_and_merge_contexts(interface)
|
80
89
|
end
|
81
90
|
rescue ActiveInteractor::Error::ContextFailure => e
|
82
91
|
context.merge!(e.context)
|
@@ -6,8 +6,8 @@ class <%= class_name %> < ApplicationOrganizer
|
|
6
6
|
|
7
7
|
<%- end -%>
|
8
8
|
<%- if interactors.any? -%>
|
9
|
-
organize <%= interactors.
|
9
|
+
organize <%= interactors.join(", ") %>
|
10
10
|
<%- else -%>
|
11
|
-
# organize
|
11
|
+
# organize :interactor_1, :interactor_2
|
12
12
|
<%- end -%>
|
13
13
|
end
|
@@ -27,6 +27,17 @@ RSpec.describe ActiveInteractor::Base do
|
|
27
27
|
subject
|
28
28
|
expect(described_class.context_class).to eq TestContext
|
29
29
|
end
|
30
|
+
|
31
|
+
# https://github.com/aaronmallen/activeinteractor/issues/168
|
32
|
+
context 'when singularized' do
|
33
|
+
let!(:singularized_class) { build_context('PlaceData') }
|
34
|
+
let(:klass) { 'PlaceData' }
|
35
|
+
|
36
|
+
it 'is expected to assign the appropriate context class' do
|
37
|
+
subject
|
38
|
+
expect(described_class.context_class).to eq PlaceData
|
39
|
+
end
|
40
|
+
end
|
30
41
|
end
|
31
42
|
|
32
43
|
context 'when passed as a symbol' do
|
@@ -36,6 +47,17 @@ RSpec.describe ActiveInteractor::Base do
|
|
36
47
|
subject
|
37
48
|
expect(described_class.context_class).to eq TestContext
|
38
49
|
end
|
50
|
+
|
51
|
+
# https://github.com/aaronmallen/activeinteractor/issues/168
|
52
|
+
context 'when singularized' do
|
53
|
+
let!(:singularized_class) { build_context('PlaceData') }
|
54
|
+
let(:klass) { :place_data }
|
55
|
+
|
56
|
+
it 'is expected to assign the appropriate context class' do
|
57
|
+
subject
|
58
|
+
expect(described_class.context_class).to eq PlaceData
|
59
|
+
end
|
60
|
+
end
|
39
61
|
end
|
40
62
|
|
41
63
|
context 'when passed as a constant' do
|
@@ -45,6 +67,17 @@ RSpec.describe ActiveInteractor::Base do
|
|
45
67
|
subject
|
46
68
|
expect(described_class.context_class).to eq TestContext
|
47
69
|
end
|
70
|
+
|
71
|
+
# https://github.com/aaronmallen/activeinteractor/issues/168
|
72
|
+
context 'when singularized' do
|
73
|
+
let!(:singularized_class) { build_context('PlaceData') }
|
74
|
+
let(:klass) { PlaceData }
|
75
|
+
|
76
|
+
it 'is expected to assign the appropriate context class' do
|
77
|
+
subject
|
78
|
+
expect(described_class.context_class).to eq PlaceData
|
79
|
+
end
|
80
|
+
end
|
48
81
|
end
|
49
82
|
end
|
50
83
|
end
|
@@ -34,6 +34,49 @@ RSpec.describe ActiveInteractor::Context::Base do
|
|
34
34
|
end
|
35
35
|
end
|
36
36
|
|
37
|
+
describe '#[]' do
|
38
|
+
subject { instance[attribute] }
|
39
|
+
|
40
|
+
context 'with class attributes []' do
|
41
|
+
let(:instance) { build_context.new }
|
42
|
+
|
43
|
+
context 'with attribute nil' do
|
44
|
+
let(:attribute) { :foo }
|
45
|
+
|
46
|
+
it { is_expected.to be_nil }
|
47
|
+
end
|
48
|
+
|
49
|
+
context 'with attribute equal to "foo"' do
|
50
|
+
let(:attribute) { :foo }
|
51
|
+
before { instance.foo = 'foo' }
|
52
|
+
|
53
|
+
it { is_expected.to eq 'foo' }
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
context 'with class attributes [:foo]' do
|
58
|
+
let!(:context_class) do
|
59
|
+
build_context do
|
60
|
+
attributes :foo
|
61
|
+
end
|
62
|
+
end
|
63
|
+
let(:instance) { context_class.new }
|
64
|
+
|
65
|
+
context 'with attribute nil' do
|
66
|
+
let(:attribute) { :foo }
|
67
|
+
|
68
|
+
it { is_expected.to be_nil }
|
69
|
+
end
|
70
|
+
|
71
|
+
context 'with attribute equal to "foo"' do
|
72
|
+
let(:attribute) { :foo }
|
73
|
+
before { instance.foo = 'foo' }
|
74
|
+
|
75
|
+
it { is_expected.to eq 'foo' }
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
37
80
|
describe '#attribute?' do
|
38
81
|
subject { instance.attribute?(attribute) }
|
39
82
|
|
@@ -127,7 +170,7 @@ RSpec.describe ActiveInteractor::Context::Base do
|
|
127
170
|
it { expect { subject }.to raise_error(ActiveInteractor::Error::ContextFailure) }
|
128
171
|
|
129
172
|
it 'is expected not to merge errors' do
|
130
|
-
expect(instance.errors).not_to receive(:merge!)
|
173
|
+
expect(instance.errors).not_to receive(:merge!).with(nil)
|
131
174
|
subject
|
132
175
|
rescue ActiveInteractor::Error::ContextFailure # rubocop:disable Lint/SuppressedException
|
133
176
|
end
|
@@ -84,6 +84,72 @@ RSpec.describe ActiveInteractor::Organizer::InteractorInterface do
|
|
84
84
|
end
|
85
85
|
end
|
86
86
|
|
87
|
+
context 'with options {:before => :some_method }' do
|
88
|
+
let(:options) { { before: :some_method } }
|
89
|
+
|
90
|
+
describe '#callbacks' do
|
91
|
+
subject { instance.callbacks }
|
92
|
+
|
93
|
+
it { is_expected.to eq(before: :some_method) }
|
94
|
+
end
|
95
|
+
|
96
|
+
describe '#perform_options' do
|
97
|
+
subject { instance.perform_options }
|
98
|
+
|
99
|
+
it { is_expected.to be_empty }
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
context 'with options {:before => -> { context.test = true } }' do
|
104
|
+
let(:options) { { before: -> { context.test = true } } }
|
105
|
+
|
106
|
+
describe '#callbacks' do
|
107
|
+
subject { instance.callbacks }
|
108
|
+
|
109
|
+
it { expect(subject[:before]).not_to be_nil }
|
110
|
+
it { expect(subject[:before]).to be_a Proc }
|
111
|
+
end
|
112
|
+
|
113
|
+
describe '#perform_options' do
|
114
|
+
subject { instance.perform_options }
|
115
|
+
|
116
|
+
it { is_expected.to be_empty }
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
context 'with options {:after => :some_method }' do
|
121
|
+
let(:options) { { after: :some_method } }
|
122
|
+
|
123
|
+
describe '#callbacks' do
|
124
|
+
subject { instance.callbacks }
|
125
|
+
|
126
|
+
it { is_expected.to eq(after: :some_method) }
|
127
|
+
end
|
128
|
+
|
129
|
+
describe '#perform_options' do
|
130
|
+
subject { instance.perform_options }
|
131
|
+
|
132
|
+
it { is_expected.to be_empty }
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
context 'with options {:after => -> { context.test = true } }' do
|
137
|
+
let(:options) { { after: -> { context.test = true } } }
|
138
|
+
|
139
|
+
describe '#callbacks' do
|
140
|
+
subject { instance.callbacks }
|
141
|
+
|
142
|
+
it { expect(subject[:after]).not_to be_nil }
|
143
|
+
it { expect(subject[:after]).to be_a Proc }
|
144
|
+
end
|
145
|
+
|
146
|
+
describe '#perform_options' do
|
147
|
+
subject { instance.perform_options }
|
148
|
+
|
149
|
+
it { is_expected.to be_empty }
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
87
153
|
context 'with options { :validate => false }' do
|
88
154
|
let(:options) { { validate: false } }
|
89
155
|
|
@@ -100,8 +166,8 @@ RSpec.describe ActiveInteractor::Organizer::InteractorInterface do
|
|
100
166
|
end
|
101
167
|
end
|
102
168
|
|
103
|
-
context 'with options { :if => :some_method, :validate => false }' do
|
104
|
-
let(:options) { { if: :some_method, validate: false } }
|
169
|
+
context 'with options { :if => :some_method, :validate => false, :before => :other_method }' do
|
170
|
+
let(:options) { { if: :some_method, validate: false, before: :other_method } }
|
105
171
|
|
106
172
|
describe '#filters' do
|
107
173
|
subject { instance.filters }
|
@@ -109,6 +175,12 @@ RSpec.describe ActiveInteractor::Organizer::InteractorInterface do
|
|
109
175
|
it { is_expected.to eq(if: :some_method) }
|
110
176
|
end
|
111
177
|
|
178
|
+
describe '#callbacks' do
|
179
|
+
subject { instance.callbacks }
|
180
|
+
|
181
|
+
it { is_expected.to eq(before: :other_method) }
|
182
|
+
end
|
183
|
+
|
112
184
|
describe '#perform_options' do
|
113
185
|
subject { instance.perform_options }
|
114
186
|
|
@@ -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,26 @@ 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
|
95
135
|
end
|
96
136
|
end
|
97
137
|
|
@@ -100,6 +140,8 @@ RSpec.describe 'A basic organizer', type: :integration do
|
|
100
140
|
let!(:test_context_class) do
|
101
141
|
build_context('TestInteractor1Context') do
|
102
142
|
attributes :foo
|
143
|
+
attributes :baz
|
144
|
+
attributes :zoo
|
103
145
|
end
|
104
146
|
end
|
105
147
|
|
@@ -111,6 +153,12 @@ RSpec.describe 'A basic organizer', type: :integration do
|
|
111
153
|
context.has_foo_as_element = context[:foo].present?
|
112
154
|
context.has_bar_as_method = context.bar.present?
|
113
155
|
context.has_bar_as_element = context[:bar].present?
|
156
|
+
context.baz = 'baz'
|
157
|
+
context.has_baz_as_method = context.baz.present?
|
158
|
+
context.has_baz_as_element = context[:baz].present?
|
159
|
+
context[:zoo] = 'zoo'
|
160
|
+
context.has_zoo_as_method = context.zoo.present?
|
161
|
+
context.has_zoo_as_element = context[:zoo].present?
|
114
162
|
end
|
115
163
|
end
|
116
164
|
end
|
@@ -127,13 +175,17 @@ RSpec.describe 'A basic organizer', type: :integration do
|
|
127
175
|
|
128
176
|
describe '.perform' do
|
129
177
|
subject(:result) { interactor_class.perform(context_attributes) }
|
130
|
-
it { is_expected.to have_attributes(foo: 'foo', bar: 'bar') }
|
178
|
+
it { is_expected.to have_attributes(foo: 'foo', bar: 'bar', baz: 'baz', zoo: 'zoo') }
|
131
179
|
|
132
180
|
it 'is expected to copy all attributes in the contexts to each interactor' do
|
133
181
|
expect(subject.has_foo_as_method).to be true
|
134
182
|
expect(subject.has_foo_as_element).to be true
|
135
183
|
expect(subject.has_bar_as_method).to be true
|
136
184
|
expect(subject.has_bar_as_element).to be true
|
185
|
+
expect(subject.has_baz_as_method).to be true
|
186
|
+
expect(subject.has_baz_as_element).to be true
|
187
|
+
expect(subject.has_zoo_as_method).to be true
|
188
|
+
expect(subject.has_zoo_as_element).to be true
|
137
189
|
end
|
138
190
|
|
139
191
|
describe '#attributes' do
|
@@ -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)
|
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(:
|
51
|
-
build_class('
|
52
|
-
|
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
|
-
|
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
|