activeinteractor 0.1.7 → 1.0.0.beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +36 -1
  3. data/README.md +397 -395
  4. data/lib/active_interactor.rb +12 -30
  5. data/lib/active_interactor/base.rb +18 -8
  6. data/lib/active_interactor/context.rb +4 -181
  7. data/lib/active_interactor/context/attributes.rb +37 -149
  8. data/lib/active_interactor/context/base.rb +141 -0
  9. data/lib/active_interactor/context/loader.rb +45 -0
  10. data/lib/active_interactor/error.rb +22 -15
  11. data/lib/active_interactor/interactor.rb +24 -57
  12. data/lib/active_interactor/interactor/callbacks.rb +64 -76
  13. data/lib/active_interactor/interactor/context.rb +97 -63
  14. data/lib/active_interactor/interactor/worker.rb +22 -65
  15. data/lib/active_interactor/organizer.rb +180 -164
  16. data/lib/active_interactor/version.rb +2 -3
  17. data/lib/rails/generators/active_interactor.rb +2 -37
  18. data/lib/rails/generators/active_interactor/application_interactor_generator.rb +23 -0
  19. data/lib/rails/generators/active_interactor/install_generator.rb +8 -12
  20. data/lib/rails/generators/active_interactor/templates/application_context.rb +4 -0
  21. data/lib/rails/generators/{templates/application_interactor.erb → active_interactor/templates/application_interactor.rb} +0 -0
  22. data/lib/rails/generators/active_interactor/templates/application_organizer.rb +4 -0
  23. data/lib/rails/generators/active_interactor/templates/initializer.erb +5 -0
  24. data/lib/rails/generators/interactor/context/rspec_generator.rb +19 -0
  25. data/lib/rails/generators/interactor/context/templates/rspec.erb +7 -0
  26. data/lib/rails/generators/interactor/context/templates/test_unit.erb +9 -0
  27. data/lib/rails/generators/interactor/context/test_unit_generator.rb +19 -0
  28. data/lib/rails/generators/interactor/context_generator.rb +19 -0
  29. data/lib/rails/generators/interactor/interactor_generator.rb +8 -3
  30. data/lib/rails/generators/interactor/organizer_generator.rb +8 -3
  31. data/lib/rails/generators/interactor/rspec_generator.rb +4 -3
  32. data/lib/rails/generators/interactor/templates/context.erb +4 -0
  33. data/lib/rails/generators/{templates → interactor/templates}/interactor.erb +0 -0
  34. data/lib/rails/generators/{templates → interactor/templates}/organizer.erb +1 -1
  35. data/lib/rails/generators/{templates → interactor/templates}/rspec.erb +0 -0
  36. data/lib/rails/generators/{templates → interactor/templates}/test_unit.erb +0 -0
  37. data/lib/rails/generators/interactor/test_unit_generator.rb +4 -3
  38. data/spec/active_interactor/base_spec.rb +51 -0
  39. data/spec/active_interactor/context/base_spec.rb +229 -0
  40. data/spec/active_interactor/error_spec.rb +43 -0
  41. data/spec/active_interactor/interactor/worker_spec.rb +89 -0
  42. data/spec/active_interactor/organizer_spec.rb +178 -0
  43. data/spec/active_interactor_spec.rb +26 -0
  44. data/spec/integration/basic_callback_integration_spec.rb +355 -0
  45. data/spec/integration/basic_context_integration_spec.rb +73 -0
  46. data/spec/integration/basic_integration_spec.rb +220 -0
  47. data/spec/integration/basic_validations_integration_spec.rb +204 -0
  48. data/spec/spec_helper.rb +44 -0
  49. data/spec/support/helpers/factories.rb +41 -0
  50. data/spec/support/shared_examples/a_class_with_interactor_callback_methods_example.rb +99 -0
  51. data/spec/support/shared_examples/a_class_with_interactor_context_methods_example.rb +58 -0
  52. data/spec/support/shared_examples/a_class_with_interactor_methods_example.rb +21 -0
  53. data/spec/support/shared_examples/a_class_with_organizer_callback_methods_example.rb +39 -0
  54. data/spec/support/spec_helpers.rb +7 -0
  55. metadata +68 -138
  56. data/lib/active_interactor/configuration.rb +0 -38
  57. data/lib/active_interactor/interactor/execution.rb +0 -24
  58. data/lib/rails/generators/templates/initializer.erb +0 -15
@@ -1,7 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActiveInteractor
4
- # The ActiveInteractor gem version
5
- # @return [String] the gem version
6
- VERSION = '0.1.7'
4
+ # @return [String] the ActiveInteractor version
5
+ VERSION = '1.0.0.beta.1'
7
6
  end
@@ -1,39 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'rails/generators/base'
4
- require 'rails/generators/named_base'
5
-
6
- require 'active_interactor'
7
-
8
- module ActiveInteractor
9
- module Generators
10
- module Generator
11
- extend ActiveSupport::Concern
12
-
13
- class_methods do
14
- def base_root
15
- __dir__
16
- end
17
-
18
- def source_root
19
- File.expand_path('templates', __dir__)
20
- end
21
- end
22
-
23
- def app_dir_name
24
- ActiveInteractor.configuration.dir_name
25
- end
26
- end
27
-
28
- class Base < ::Rails::Generators::Base
29
- include Generator
30
- end
31
-
32
- class NamedBase < ::Rails::Generators::NamedBase
33
- include Generator
34
- end
35
- end
36
- end
37
-
38
- Dir[File.expand_path('active_interactor/*.rb', __dir__)].each { |file| require file }
39
- Dir[File.expand_path('interactor/*.rb', __dir__)].each { |file| require file }
3
+ Dir[File.expand_path('active_interactor/*.rb', __dir__)].sort.each { |file| require file }
4
+ Dir[File.expand_path('interactor/*.rb', __dir__)].sort.each { |file| require file }
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rails/generators/base'
4
+
5
+ module ActiveInteractor
6
+ module Generators
7
+ class ApplicationInteractorGenerator < ::Rails::Generators::Base
8
+ source_root File.expand_path('templates', __dir__)
9
+
10
+ def create_application_interactor
11
+ template 'application_interactor.rb', Rails.root.join('app', 'interactors', 'application_interactor.rb')
12
+ end
13
+
14
+ def create_application_organizer
15
+ template 'application_organizer.rb', Rails.root.join('app', 'interactors', 'application_organizer.rb')
16
+ end
17
+
18
+ def create_application_context
19
+ template 'application_context.rb', Rails.root.join('app', 'interactors', 'application_context.rb')
20
+ end
21
+ end
22
+ end
23
+ end
@@ -1,30 +1,26 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative '../active_interactor'
3
+ require 'rails/generators/base'
4
4
 
5
5
  module ActiveInteractor
6
6
  module Generators
7
- class InstallGenerator < Base
7
+ class InstallGenerator < ::Rails::Generators::Base
8
+ source_root File.expand_path('templates', __dir__)
8
9
  desc 'Install ActiveInteractor'
9
- argument :directory, type: :string, default: 'interactors', banner: 'directory'
10
10
 
11
11
  def create_initializer
12
12
  template 'initializer.erb', Rails.root.join('config', 'initializers', 'active_interactor.rb')
13
13
  end
14
14
 
15
- def create_application_interactor
16
- template 'application_interactor.erb', Rails.root.join('app', directory, 'application_interactor.rb')
17
- end
18
-
19
- def create_interactor_concerns
20
- create_file Rails.root.join('app', directory, 'concerns', '.keep')
15
+ def create_application_interactor_and_context
16
+ generate :'active_interactor:application_interactor'
21
17
  end
22
18
 
23
19
  def autoload_interactors
24
20
  application do
25
- <<~CONFIG
26
- # autoload interactors
27
- config.autoload_paths += %w[app/#{directory}]
21
+ <<-CONFIG
22
+ # autoload interactors
23
+ config.autoload_paths += %w[app/interactors]
28
24
 
29
25
  CONFIG
30
26
  end
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ class ApplicationContext < ActiveInteractor::Context::Base
4
+ end
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ class ApplicationOrganizer < ActiveInteractor::Organizer
4
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_interactor'
4
+
5
+ ActiveInteractor.logger = ::Rails.logger
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rails/generators/named_base'
4
+
5
+ module Interactor
6
+ module Context
7
+ module Generators
8
+ class RspecGenerator < ::Rails::Generators::NamedBase
9
+ source_root File.expand_path('templates', __dir__)
10
+ desc 'Generate an interactor spec'
11
+
12
+ def create_spec
13
+ file_path = Rails.root.join('spec/interactors', File.join(class_path), "#{file_name}_context_spec.rb")
14
+ template 'rspec.erb', file_path
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rails_helper'
4
+
5
+ RSpec.describe <%= class_name %>Context, type: :interactor do
6
+ pending "add some examples to (or delete) #{__FILE__}"
7
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'test_helper'
4
+
5
+ class <%= class_name %>ContextTest < ActiveSupport::TestCase
6
+ # test "the truth" do
7
+ # assert true
8
+ # end
9
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rails/generators/named_base'
4
+
5
+ module Interactor
6
+ module Context
7
+ module Generators
8
+ class TestUnitGenerator < ::Rails::Generators::NamedBase
9
+ source_root File.expand_path('templates', __dir__)
10
+ desc 'Generate an interactor unit test'
11
+
12
+ def create_test
13
+ file_path = Rails.root.join('test/interactors', File.join(class_path), "#{file_name}_context_test.rb")
14
+ template 'test_unit.erb', file_path
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rails/generators/named_base'
4
+
5
+ module Interactor
6
+ module Generators
7
+ class ContextGenerator < ::Rails::Generators::NamedBase
8
+ source_root File.expand_path('templates', __dir__)
9
+ desc 'Generate an interactor context'
10
+ argument :interactors, type: :array, default: [], banner: 'name name'
11
+
12
+ def create_context
13
+ template 'context.erb', Rails.root.join('app/interactors', File.join(class_path), "#{file_name}_context.rb")
14
+ end
15
+
16
+ hook_for :test_framework, in: :'interactor:context'
17
+ end
18
+ end
19
+ end
@@ -1,12 +1,17 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative '../active_interactor'
3
+ require 'rails/generators/named_base'
4
4
 
5
- class InteractorGenerator < ActiveInteractor::Generators::NamedBase
5
+ class InteractorGenerator < ::Rails::Generators::NamedBase
6
+ source_root File.expand_path('templates', __dir__)
6
7
  desc 'Generate an interactor'
7
8
 
8
9
  def create_interactor
9
- template 'interactor.erb', Rails.root.join('app', app_dir_name, File.join(class_path), "#{file_name}.rb")
10
+ template 'interactor.erb', Rails.root.join('app/interactors', File.join(class_path), "#{file_name}.rb")
11
+ end
12
+
13
+ def create_context
14
+ generate :'interactor:context', class_name
10
15
  end
11
16
 
12
17
  hook_for :test_framework, in: :interactor
@@ -1,15 +1,20 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative '../active_interactor'
3
+ require 'rails/generators/named_base'
4
4
 
5
5
  module Interactor
6
6
  module Generators
7
- class OrganizerGenerator < ActiveInteractor::Generators::NamedBase
7
+ class OrganizerGenerator < ::Rails::Generators::NamedBase
8
+ source_root File.expand_path('templates', __dir__)
8
9
  desc 'Generate an interactor organizer'
9
10
  argument :interactors, type: :array, default: [], banner: 'name name'
10
11
 
11
12
  def create_organizer
12
- template 'organizer.erb', Rails.root.join('app', app_dir_name, File.join(class_path), "#{file_name}.rb")
13
+ template 'organizer.erb', Rails.root.join('app/interactors', File.join(class_path), "#{file_name}.rb")
14
+ end
15
+
16
+ def create_context
17
+ generate :'interactor:context', class_name
13
18
  end
14
19
 
15
20
  hook_for :test_framework, in: :interactor
@@ -1,14 +1,15 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative '../active_interactor'
3
+ require 'rails/generators/named_base'
4
4
 
5
5
  module Interactor
6
6
  module Generators
7
- class RspecGenerator < ActiveInteractor::Generators::NamedBase
7
+ class RspecGenerator < ::Rails::Generators::NamedBase
8
+ source_root File.expand_path('templates', __dir__)
8
9
  desc 'Generate an interactor spec'
9
10
 
10
11
  def create_spec
11
- template 'rspec.erb', Rails.root.join('spec', app_dir_name, File.join(class_path), "#{file_name}_spec.rb")
12
+ template 'rspec.erb', Rails.root.join('spec/interactors', File.join(class_path), "#{file_name}_spec.rb")
12
13
  end
13
14
  end
14
15
  end
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ class <%= class_name %>Context < ApplicationContext
4
+ end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- class <%= class_name %> < ActiveInteractor::Organizer
3
+ class <%= class_name %> < ApplicationOrganizer
4
4
 
5
5
  <%- if interactors.any? -%>
6
6
  organize <%= interactors.map(&:classify).join(", ") %>
@@ -1,14 +1,15 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative '../active_interactor'
3
+ require 'rails/generators/named_base'
4
4
 
5
5
  module Interactor
6
6
  module Generators
7
- class TestUnitGenerator < ActiveInteractor::Generators::NamedBase
7
+ class TestUnitGenerator < ::Rails::Generators::NamedBase
8
+ source_root File.expand_path('templates', __dir__)
8
9
  desc 'Generate an interactor unit test'
9
10
 
10
11
  def create_test
11
- template 'test_unit.erb', Rails.root.join('test', app_dir_name, File.join(class_path), "#{file_name}_test.rb")
12
+ template 'test_unit.erb', Rails.root.join('test/interactors', File.join(class_path), "#{file_name}_test.rb")
12
13
  end
13
14
  end
14
15
  end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ RSpec.describe ActiveInteractor::Base do
6
+ let(:interactor_class) { described_class }
7
+ include_examples 'a class with interactor methods'
8
+ include_examples 'a class with interactor callback methods'
9
+ include_examples 'a class with interactor context methods'
10
+
11
+ describe '.contextualize_with' do
12
+ subject { described_class.contextualize_with(klass) }
13
+
14
+ context 'with an class that does not exist' do
15
+ let(:klass) { 'SomeClassThatDoesNotExist' }
16
+
17
+ it { expect { subject }.to raise_error(ActiveInteractor::Error::InvalidContextClass) }
18
+ end
19
+
20
+ context 'with context class TestContext' do
21
+ before { build_context }
22
+
23
+ context 'when passed as a string' do
24
+ let(:klass) { 'TestContext' }
25
+
26
+ it 'assigns the context class' do
27
+ subject
28
+ expect(described_class.context_class).to eq TestContext
29
+ end
30
+ end
31
+
32
+ context 'when passed as a symbol' do
33
+ let(:klass) { :test_context }
34
+
35
+ it 'assigns the context class' do
36
+ subject
37
+ expect(described_class.context_class).to eq TestContext
38
+ end
39
+ end
40
+
41
+ context 'when passed as a constant' do
42
+ let(:klass) { TestContext }
43
+
44
+ it 'assigns the context class' do
45
+ subject
46
+ expect(described_class.context_class).to eq TestContext
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,229 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ RSpec.describe ActiveInteractor::Context::Base do
6
+ after(:each) { described_class.instance_variable_set('@__attributes', []) }
7
+
8
+ describe '#attributes' do
9
+ context 'when no arguments are passed' do
10
+ subject { described_class.attributes }
11
+ it { is_expected.to eq [] }
12
+
13
+ context 'when an attribute :foo was previously defined' do
14
+ before { described_class.instance_variable_set('@__attributes', %i[foo]) }
15
+
16
+ it { is_expected.to eq %i[foo] }
17
+ end
18
+ end
19
+
20
+ context 'when given arguments :foo and :bar' do
21
+ subject { described_class.attributes(:foo, :bar) }
22
+
23
+ it { is_expected.to eq %i[bar foo] }
24
+
25
+ context 'when an attribute :foo was previously defined' do
26
+ before { described_class.instance_variable_set('@__attributes', %i[foo]) }
27
+
28
+ it { is_expected.to eq %i[bar foo] }
29
+ end
30
+ end
31
+ end
32
+
33
+ describe '.attributes' do
34
+ subject { instance.attributes }
35
+
36
+ context 'with class attributes []' do
37
+ context 'with an instance having attributes { :foo => "foo", :bar => "bar", :baz => "baz" }' do
38
+ let(:instance) { described_class.new(foo: 'foo', bar: 'bar', baz: 'baz') }
39
+
40
+ it { is_expected.to be_a Hash }
41
+ it { is_expected.to be_empty }
42
+ end
43
+ end
44
+
45
+ context 'with class attributes [:foo, :bar, :baz]' do
46
+ before { described_class.attributes(:foo, :bar, :baz) }
47
+
48
+ context 'with an instance having attributes { :foo => "foo", :bar => "bar", :baz => "baz" }' do
49
+ let(:instance) { described_class.new(foo: 'foo', bar: 'bar', baz: 'baz') }
50
+
51
+ it { is_expected.to be_a Hash }
52
+ it { is_expected.to eq(bar: 'bar', baz: 'baz', foo: 'foo') }
53
+ end
54
+ end
55
+ end
56
+
57
+ describe '#called!' do
58
+ subject do
59
+ instance.called!(interactor1)
60
+ instance.called!(interactor2)
61
+ end
62
+
63
+ let(:instance) { described_class.new }
64
+ let(:interactor1) { double(:interactor1) }
65
+ let(:interactor2) { double(:interactor2) }
66
+
67
+ it 'is expected to append interactors to instance variable _called' do
68
+ expect { subject }.to change { instance.send(:_called) }
69
+ .from([])
70
+ .to([interactor1, interactor2])
71
+ end
72
+ end
73
+
74
+ describe '#fail!' do
75
+ subject { instance.fail!(errors) }
76
+ let(:instance) { described_class.new }
77
+
78
+ context 'with errors equal to nil' do
79
+ let(:errors) { nil }
80
+
81
+ it { expect { subject }.to raise_error(ActiveInteractor::Error::ContextFailure) }
82
+
83
+ it 'is expected not to merge errors' do
84
+ expect(instance.errors).not_to receive(:merge!)
85
+ subject
86
+ rescue ActiveInteractor::Error::ContextFailure # rubocop:disable Lint/SuppressedException
87
+ end
88
+
89
+ it 'is expected to be a failure' do
90
+ subject
91
+ rescue ActiveInteractor::Error::ContextFailure
92
+ expect(instance).to be_a_failure
93
+ end
94
+ end
95
+
96
+ context 'with errors from another instance on the attribute :foo' do
97
+ let(:errors) { instance2.errors }
98
+ let(:instance2) { described_class.new }
99
+
100
+ before { instance2.errors.add(:foo, 'foo') }
101
+
102
+ it { expect { subject }.to raise_error(ActiveInteractor::Error::ContextFailure) }
103
+
104
+ it 'is expected to merge errors' do
105
+ subject
106
+ rescue ActiveInteractor::Error::ContextFailure
107
+ expect(instance.errors[:foo]).to eq instance2.errors[:foo]
108
+ end
109
+
110
+ it 'is expected to be a failure' do
111
+ subject
112
+ rescue ActiveInteractor::Error::ContextFailure
113
+ expect(instance).to be_a_failure
114
+ end
115
+ end
116
+
117
+ context 'with errors "foo"' do
118
+ let(:errors) { 'foo' }
119
+
120
+ it { expect { subject }.to raise_error(ActiveInteractor::Error::ContextFailure) }
121
+
122
+ it 'is expected to have error on :context equal to "foo"' do
123
+ subject
124
+ rescue ActiveInteractor::Error::ContextFailure
125
+ expect(instance.errors[:context]).to include 'foo'
126
+ end
127
+
128
+ it 'is expected to be a failure' do
129
+ subject
130
+ rescue ActiveInteractor::Error::ContextFailure
131
+ expect(instance).to be_a_failure
132
+ end
133
+ end
134
+ end
135
+
136
+ describe '#new' do
137
+ subject { described_class.new(attributes) }
138
+
139
+ context 'with a previous instance having attributes { :foo => "foo" }' do
140
+ let(:attributes) { described_class.new(foo: 'foo') }
141
+
142
+ it { is_expected.to be_a described_class }
143
+ it { is_expected.to have_attributes(foo: 'foo') }
144
+
145
+ context 'having instance variable @_called equal to ["foo"]' do
146
+ before { attributes.instance_variable_set('@_called', %w[foo]) }
147
+
148
+ it { is_expected.to be_a described_class }
149
+ it { is_expected.to have_attributes(foo: 'foo') }
150
+ it 'is expected to preserve @_called instance variable' do
151
+ expect(subject.instance_variable_get('@_called')).to eq %w[foo]
152
+ end
153
+ end
154
+
155
+ context 'having instance variable @_failed equal to true' do
156
+ before { attributes.instance_variable_set('@_failed', true) }
157
+
158
+ it { is_expected.to be_a described_class }
159
+ it { is_expected.to have_attributes(foo: 'foo') }
160
+ it 'is expected to preserve @_failed instance variable' do
161
+ expect(subject.instance_variable_get('@_failed')).to eq true
162
+ end
163
+ end
164
+
165
+ context 'having instance variable @_rolled_back equal to true' do
166
+ before { attributes.instance_variable_set('@_rolled_back', true) }
167
+
168
+ it { is_expected.to be_a described_class }
169
+ it { is_expected.to have_attributes(foo: 'foo') }
170
+ it 'is expected to preserve @_rolled_back instance variable' do
171
+ expect(subject.instance_variable_get('@_rolled_back')).to eq true
172
+ end
173
+ end
174
+ end
175
+ end
176
+
177
+ describe '#failure?' do
178
+ subject { instance.failure? }
179
+ let(:instance) { described_class.new }
180
+
181
+ it { is_expected.to eq false }
182
+
183
+ context 'when context has failed' do
184
+ before { instance.instance_variable_set('@_failed', true) }
185
+
186
+ it { is_expected.to eq true }
187
+ end
188
+ end
189
+
190
+ describe '#rollback!' do
191
+ subject { instance.rollback! }
192
+ let(:instance) { described_class.new }
193
+
194
+ context 'with #called! interactors' do
195
+ let(:interactor1) { double(:interactor1) }
196
+ let(:interactor2) { double(:interactor2) }
197
+
198
+ before do
199
+ allow(instance).to receive(:_called).and_return([interactor1, interactor2])
200
+ end
201
+
202
+ it 'is expected to rollback each interactor in reverse order' do
203
+ expect(interactor2).to receive(:rollback).once.with(no_args).ordered
204
+ expect(interactor1).to receive(:rollback).once.with(no_args).ordered
205
+ subject
206
+ end
207
+
208
+ it 'is expected to ignore subsequent attempts' do
209
+ expect(interactor2).to receive(:rollback).once
210
+ expect(interactor1).to receive(:rollback).once
211
+ subject
212
+ subject
213
+ end
214
+ end
215
+ end
216
+
217
+ describe '#success?' do
218
+ subject { instance.success? }
219
+ let(:instance) { described_class.new }
220
+
221
+ it { is_expected.to eq true }
222
+
223
+ context 'when context has failed' do
224
+ before { instance.instance_variable_set('@_failed', true) }
225
+
226
+ it { is_expected.to eq false }
227
+ end
228
+ end
229
+ end