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.
Files changed (35) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +59 -1
  3. data/README.md +20 -5
  4. data/lib/active_interactor.rb +1 -0
  5. data/lib/active_interactor/config.rb +1 -1
  6. data/lib/active_interactor/context/attributes.rb +56 -11
  7. data/lib/active_interactor/context/base.rb +1 -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 +2 -1
  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 +25 -8
  15. data/lib/active_interactor/organizer/interactor_interface_collection.rb +1 -0
  16. data/lib/active_interactor/organizer/perform.rb +11 -2
  17. data/lib/active_interactor/rails/orm/dynamoid.rb +5 -0
  18. data/lib/active_interactor/rails/orm/mongoid.rb +5 -0
  19. data/lib/active_interactor/version.rb +1 -1
  20. data/lib/rails/generators/templates/organizer.erb +2 -2
  21. data/spec/active_interactor/base_spec.rb +33 -0
  22. data/spec/active_interactor/context/base_spec.rb +44 -1
  23. data/spec/active_interactor/organizer/interactor_interface_spec.rb +74 -2
  24. data/spec/integration/a_basic_organizer_spec.rb +53 -1
  25. data/spec/integration/active_record_integration_spec.rb +8 -342
  26. data/spec/integration/an_organizer_with_failing_nested_organizer_spec.rb +47 -0
  27. data/spec/integration/an_organizer_with_options_callbacks_spec.rb +63 -0
  28. data/spec/spec_helper.rb +18 -8
  29. data/spec/support/shared_examples/a_class_that_extends_active_interactor_models_example.rb +81 -0
  30. metadata +40 -41
  31. data/spec/support/coverage.rb +0 -4
  32. data/spec/support/coverage/reporters.rb +0 -11
  33. data/spec/support/coverage/reporters/codacy.rb +0 -39
  34. data/spec/support/coverage/reporters/simple_cov.rb +0 -54
  35. 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.classify.safe_constantize
51
+ @interactor_class = interactor_class.to_s.camelize.safe_constantize
44
52
  @filters = options.select { |key, _value| CONDITIONAL_FILTERS.include?(key) }
45
- @perform_options = options.reject { |key, _value| CONDITIONAL_FILTERS.include?(key) }
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, filters[:if]) == false
61
- return if check_conditionals(target, filters[:unless]) == true
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
- return unless filter
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(filter) if filter.is_a?(Symbol)
74
- return target.instance_exec(&filter) if filter.is_a?(Proc)
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
- result = execute_interactor_with_callbacks(interface, true)
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)
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'dynamoid/document'
4
+
5
+ Dynamoid::Document::ClassMethods.include ActiveInteractor::Models
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ ActiveSupport.on_load(:mongoid) do
4
+ Mongoid::Document::ClassMethods.include ActiveInteractor::Models
5
+ end
@@ -3,5 +3,5 @@
3
3
  module ActiveInteractor
4
4
  # The ActiveInteractor version
5
5
  # @return [String] the ActiveInteractor version
6
- VERSION = '1.0.1'
6
+ VERSION = '1.1.0'
7
7
  end
@@ -6,8 +6,8 @@ class <%= class_name %> < ApplicationOrganizer
6
6
 
7
7
  <%- end -%>
8
8
  <%- if interactors.any? -%>
9
- organize <%= interactors.map(&:classify).join(", ") %>
9
+ organize <%= interactors.join(", ") %>
10
10
  <%- else -%>
11
- # organize Interactor1, Interactor2
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) 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