activeinteractor 1.0.1 → 1.0.2
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 +18 -1
- data/README.md +19 -3
- data/lib/active_interactor/context/attributes.rb +22 -0
- data/lib/active_interactor/context/status.rb +0 -18
- data/lib/active_interactor/models.rb +12 -28
- 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/spec/integration/active_record_integration_spec.rb +8 -342
- data/spec/spec_helper.rb +19 -8
- data/spec/support/shared_examples/a_class_that_extends_active_interactor_models_example.rb +81 -0
- metadata +40 -46
- 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
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f83ff894cc3ce150f3a97ab2b006035acf9f27c181040f02c04e0058b3d3dd49
|
4
|
+
data.tar.gz: fd8d75ec724d1b35eec4b7cd2017c74a9fb61c54209f58830a7ffd5b780b5ba8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: '085c0039e71216c6c3dc734a4aad4bb30fc0bf5fa5babe8a9b72e2b961c5066732c5bdfbc5eb1566ababc42cbcf38e1b5e3d87e61a94251e08eec2c13a99c524'
|
7
|
+
data.tar.gz: fa572ba8068e8856bfbc405500cffd4b398f47c67d7b665a02ead9a5d701cdb62983f17e6eb6df7c13f7b29b981c58054a7613b6eb4970a488c45d15acc7bc40
|
data/CHANGELOG.md
CHANGED
@@ -7,6 +7,22 @@ and this project adheres to [Semantic Versioning].
|
|
7
7
|
|
8
8
|
## [Unreleased]
|
9
9
|
|
10
|
+
## [v1.0.2] - 2020-02-04
|
11
|
+
|
12
|
+
### Added
|
13
|
+
|
14
|
+
- Support for `Dynamoid` ORM
|
15
|
+
- Support for `Mongoid` ORM
|
16
|
+
|
17
|
+
### Changed
|
18
|
+
|
19
|
+
- `ActiveInteractor::Models#acts_as_context` no longer includes `ActiveModel::Validations`
|
20
|
+
- `ActiveInteractor::Models#acts_as_context` now includes `ActiveInteractor::Context::Attributes`
|
21
|
+
|
22
|
+
### Removed
|
23
|
+
|
24
|
+
- `ActiveInteractor::Models::InstanceMethods#merge!`
|
25
|
+
|
10
26
|
## [v1.0.1] - 2020-01-28
|
11
27
|
|
12
28
|
### Added
|
@@ -159,7 +175,8 @@ and this project adheres to [Semantic Versioning].
|
|
159
175
|
|
160
176
|
<!-- versions -->
|
161
177
|
|
162
|
-
[Unreleased]: https://github.com/aaronmallen/activeinteractor/compare/v1.0.
|
178
|
+
[Unreleased]: https://github.com/aaronmallen/activeinteractor/compare/v1.0.2...HEAD
|
179
|
+
[v1.0.2]: https://github.com/aaronmallen/activeinteractor/compare/v1.0.1...v1.0.2
|
163
180
|
[v1.0.1]: https://github.com/aaronmallen/activeinteractor/compare/v1.0.0...v1.0.1
|
164
181
|
[v1.0.0]: https://github.com/aaronmallen/activeinteractor/compare/v0.1.7...v1.0.0
|
165
182
|
[v0.1.7]: https://github.com/aaronmallen/activeinteractor/compare/v0.1.6...v0.1.7
|
data/README.md
CHANGED
@@ -1,17 +1,28 @@
|
|
1
1
|
# ActiveInteractor
|
2
2
|
|
3
3
|
[](https://rubygems.org/gems/activeinteractor)
|
4
|
-
[](https://github.com/aaronmallen/activeinteractor/blob/master/LICENSE)
|
5
|
-
[](https://depfu.com/github/aaronmallen/activeinteractor)
|
6
|
-
|
7
4
|
[](https://github.com/aaronmallen/activeinteractor/actions)
|
8
5
|
[](https://www.codacy.com/manual/aaronmallen/activeinteractor?utm_source=github.com&utm_medium=referral&utm_content=aaronmallen/activeinteractor&utm_campaign=Badge_Grade)
|
9
6
|
[](https://www.codacy.com/manual/aaronmallen/activeinteractor?utm_source=github.com&utm_medium=referral&utm_content=aaronmallen/activeinteractor&utm_campaign=Badge_Coverage)
|
7
|
+
[](https://depfu.com/github/aaronmallen/activeinteractor)
|
8
|
+
[](https://www.rubydoc.info/gems/activeinteractor)
|
9
|
+
[](http://inch-ci.org/github/aaronmallen/activeinteractor)
|
10
|
+
[](https://github.com/aaronmallen/activeinteractor/blob/master/LICENSE)
|
10
11
|
|
11
12
|
An implementation of the [command pattern] for Ruby with [ActiveModel::Validations] inspired by the
|
12
13
|
[interactor][collective_idea_interactors] gem. Rich support for attributes, callbacks, and validations,
|
13
14
|
and thread safe performance methods.
|
14
15
|
|
16
|
+
Reduce controller bloat with procedural service objects. Checkout this [Medium article] for a crash
|
17
|
+
course on how to use ActiveInteractors. Read the [wiki] for detailed usage information.
|
18
|
+
|
19
|
+
## Features
|
20
|
+
|
21
|
+
* [Context validation][wiki_context_validation]
|
22
|
+
* [Callbacks][wiki_callbacks]
|
23
|
+
* Thread safe performance calls
|
24
|
+
* Organize multiple interactors [conditionally][wiki_organizers_conditionally] or in [parallel][wiki_organizers_parallel]
|
25
|
+
|
15
26
|
## Getting Started
|
16
27
|
|
17
28
|
Add this line to your application's Gemfile:
|
@@ -68,6 +79,11 @@ The gem is available as open source under the terms of the [MIT License][mit_lic
|
|
68
79
|
[business_logic_wikipedia]: https://en.wikipedia.org/wiki/Business_logic
|
69
80
|
[collective_idea_interactors]: https://github.com/collectiveidea/interactor
|
70
81
|
[command pattern]: https://en.wikipedia.org/wiki/Command_pattern
|
82
|
+
[Medium article]: https://medium.com/@aaronmallen/activeinteractor-8557c0dc78db
|
71
83
|
[mit_license]: https://opensource.org/licenses/MIT
|
72
84
|
[ruby docs]: https://www.rubydoc.info/gems/activeinteractor
|
73
85
|
[wiki]: https://github.com/aaronmallen/activeinteractor/wiki
|
86
|
+
[wiki_callbacks]: https://github.com/aaronmallen/activeinteractor/wiki/Callbacks
|
87
|
+
[wiki_context_validation]: https://github.com/aaronmallen/activeinteractor/wiki/Context#validating-the-context
|
88
|
+
[wiki_organizers_conditionally]: https://github.com/aaronmallen/activeinteractor/wiki/Interactors#organizing-interactors-conditionally
|
89
|
+
[wiki_organizers_parallel]: https://github.com/aaronmallen/activeinteractor/wiki/Interactors#running-interactors-in-parallel
|
@@ -53,6 +53,7 @@ module ActiveInteractor
|
|
53
53
|
merge_errors!(context.errors) if context.respond_to?(:errors)
|
54
54
|
copy_flags!(context)
|
55
55
|
copy_called!(context)
|
56
|
+
context = context_attributes_as_hash(context) || {}
|
56
57
|
super
|
57
58
|
|
58
59
|
merge_attribute_values(context)
|
@@ -114,6 +115,27 @@ module ActiveInteractor
|
|
114
115
|
|
115
116
|
private
|
116
117
|
|
118
|
+
def _called
|
119
|
+
@_called ||= []
|
120
|
+
end
|
121
|
+
|
122
|
+
def context_attributes_as_hash(context)
|
123
|
+
return context.to_h if context&.respond_to?(:to_h)
|
124
|
+
return context.attributes.to_h if context.respond_to?(:attributes)
|
125
|
+
end
|
126
|
+
|
127
|
+
def copy_called!(context)
|
128
|
+
value = context.instance_variable_get('@_called') || []
|
129
|
+
instance_variable_set('@_called', value)
|
130
|
+
end
|
131
|
+
|
132
|
+
def copy_flags!(context)
|
133
|
+
%w[_failed _rolled_back].each do |flag|
|
134
|
+
value = context.instance_variable_get("@#{flag}")
|
135
|
+
instance_variable_set("@#{flag}", value)
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
117
139
|
def merge_attribute_values(context)
|
118
140
|
return unless context
|
119
141
|
|
@@ -90,24 +90,6 @@ module ActiveInteractor
|
|
90
90
|
!failure?
|
91
91
|
end
|
92
92
|
alias successful? success?
|
93
|
-
|
94
|
-
private
|
95
|
-
|
96
|
-
def _called
|
97
|
-
@_called ||= []
|
98
|
-
end
|
99
|
-
|
100
|
-
def copy_called!(context)
|
101
|
-
value = context.instance_variable_get('@_called') || []
|
102
|
-
instance_variable_set('@_called', value)
|
103
|
-
end
|
104
|
-
|
105
|
-
def copy_flags!(context)
|
106
|
-
%w[_failed _rolled_back].each do |flag|
|
107
|
-
value = context.instance_variable_get("@#{flag}")
|
108
|
-
instance_variable_set("@#{flag}", value)
|
109
|
-
end
|
110
|
-
end
|
111
93
|
end
|
112
94
|
end
|
113
95
|
end
|
@@ -13,39 +13,21 @@ module ActiveInteractor
|
|
13
13
|
# @author Aaron Allen <hello@aaronmallen.me>
|
14
14
|
# @since 1.0.0
|
15
15
|
module InstanceMethods
|
16
|
-
def initialize(
|
17
|
-
|
18
|
-
|
19
|
-
attributes_as_hash = context_attributes_as_hash(attributes)
|
20
|
-
super(attributes_as_hash)
|
21
|
-
end
|
22
|
-
|
23
|
-
# Merge an instance of model class into the calling model class instance
|
24
|
-
#
|
25
|
-
# @see Context::Attributes#merge!
|
26
|
-
#
|
27
|
-
# @param context [Class] a {Base context} instance to be merged
|
28
|
-
# @return [self] the {Base context} instance
|
29
|
-
def merge!(context)
|
30
|
-
copy_flags!(context)
|
31
|
-
context.each_pair do |key, value|
|
32
|
-
self[key] = value unless value.nil?
|
33
|
-
end
|
34
|
-
self
|
35
|
-
end
|
36
|
-
|
37
|
-
private
|
38
|
-
|
39
|
-
def context_attributes_as_hash(attributes)
|
40
|
-
return attributes.to_h if attributes&.respond_to?(:to_h)
|
41
|
-
return attributes.attributes.to_h if attributes.respond_to?(:attributes)
|
16
|
+
def initialize(*)
|
17
|
+
@attributes = self.class._default_attributes&.deep_dup
|
18
|
+
super
|
42
19
|
end
|
43
20
|
end
|
44
21
|
|
45
22
|
# Include methods needed for a {Context::Base context} class to function properly.
|
46
23
|
#
|
24
|
+
# @note You must include ActiveModel::Model and ActiveModel::Attributes or a similar implementation for
|
25
|
+
# the object to function properly.
|
26
|
+
#
|
47
27
|
# @example
|
48
28
|
# class User
|
29
|
+
# include ActiveModel::Model
|
30
|
+
# include ActiveModel::Attributes
|
49
31
|
# extend ActiveInteractor::Models
|
50
32
|
# acts_as_context
|
51
33
|
# end
|
@@ -55,9 +37,11 @@ module ActiveInteractor
|
|
55
37
|
# end
|
56
38
|
def acts_as_context
|
57
39
|
class_eval do
|
58
|
-
|
59
|
-
|
40
|
+
extend ActiveInteractor::Context::Attributes::ClassMethods
|
41
|
+
|
42
|
+
include ActiveInteractor::Context::Attributes
|
60
43
|
include ActiveInteractor::Context::Status
|
44
|
+
include ActiveInteractor::Models::InstanceMethods
|
61
45
|
delegate :each_pair, to: :attributes
|
62
46
|
end
|
63
47
|
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)
|
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
|
data/spec/spec_helper.rb
CHANGED
@@ -1,13 +1,24 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
3
|
+
begin
|
4
|
+
require 'codacy-coverage'
|
5
|
+
require 'simplecov'
|
6
|
+
|
7
|
+
SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter.new(
|
8
|
+
[
|
9
|
+
SimpleCov::Formatter::HTMLFormatter,
|
10
|
+
Codacy::Formatter
|
11
|
+
]
|
12
|
+
)
|
13
|
+
|
14
|
+
SimpleCov.start do
|
15
|
+
enable_coverage :branch
|
16
|
+
add_filter '/spec/'
|
17
|
+
add_filter '/lib/rails/**/*.rb'
|
18
|
+
track_files '/lib/**/*.rb'
|
19
|
+
end
|
20
|
+
rescue LoadError
|
21
|
+
puts 'Skipping coverage...'
|
11
22
|
end
|
12
23
|
|
13
24
|
require 'bundler/setup'
|
@@ -0,0 +1,81 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
RSpec.shared_examples 'A class that extends ActiveInteractor::Models' do
|
4
|
+
it { expect(model_class).to respond_to :attribute }
|
5
|
+
it { expect(model_class).to respond_to :attributes }
|
6
|
+
|
7
|
+
describe 'as an instance' do
|
8
|
+
subject { model_class.new }
|
9
|
+
|
10
|
+
it { is_expected.to respond_to :attributes }
|
11
|
+
it { is_expected.to respond_to :called! }
|
12
|
+
it { is_expected.to respond_to :fail! }
|
13
|
+
it { is_expected.to respond_to :fail? }
|
14
|
+
it { is_expected.to respond_to :failure? }
|
15
|
+
it { is_expected.to respond_to :merge! }
|
16
|
+
it { is_expected.to respond_to :rollback! }
|
17
|
+
it { is_expected.to respond_to :success? }
|
18
|
+
it { is_expected.to respond_to :successful? }
|
19
|
+
end
|
20
|
+
|
21
|
+
describe '.new' do
|
22
|
+
subject { model_class.new(attributes) }
|
23
|
+
|
24
|
+
describe 'with arguments {:foo => "foo"}' do
|
25
|
+
let(:attributes) { { foo: 'foo' } }
|
26
|
+
|
27
|
+
it { is_expected.to have_attributes(foo: 'foo') }
|
28
|
+
end
|
29
|
+
|
30
|
+
describe 'with arguments {:bar => "bar"}' do
|
31
|
+
let(:attributes) { model_class.new(bar: 'bar') }
|
32
|
+
|
33
|
+
it { expect { subject }.to raise_error(ActiveModel::UnknownAttributeError) }
|
34
|
+
end
|
35
|
+
|
36
|
+
describe 'with arguments <#ModelClass foo="foo">' do
|
37
|
+
let(:other_instance) { model_class.new(foo: 'foo') }
|
38
|
+
let(:attributes) { other_instance }
|
39
|
+
|
40
|
+
it { is_expected.to have_attributes(foo: 'foo') }
|
41
|
+
|
42
|
+
context 'with argument instance having @_failed eq to true' do
|
43
|
+
before { other_instance.instance_variable_set('@_failed', true) }
|
44
|
+
|
45
|
+
it { is_expected.to be_failure }
|
46
|
+
end
|
47
|
+
|
48
|
+
context 'with argument instance having @_called eq to ["foo"]' do
|
49
|
+
before { other_instance.instance_variable_set('@_called', %w[foo]) }
|
50
|
+
|
51
|
+
it 'is expected to have instance variable @_called eq to ["foo"]' do
|
52
|
+
expect(subject.instance_variable_get('@_called')).to eq %w[foo]
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
context 'with argument instance having @_rolled_back eq to true' do
|
57
|
+
before { other_instance.instance_variable_set('@_rolled_back', true) }
|
58
|
+
|
59
|
+
it 'is expected to have instance variable @_rolled_back eq to true' do
|
60
|
+
expect(subject.instance_variable_get('@_rolled_back')).to eq true
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
describe 'with arguments <#OtherClass bar="bar">' do
|
66
|
+
let(:attributes) { model_class.new(other_instance) }
|
67
|
+
let!(:other_class) do
|
68
|
+
build_class('OtherClass') do
|
69
|
+
include ActiveModel::Attributes
|
70
|
+
include ActiveModel::Model
|
71
|
+
extend ActiveInteractor::Models
|
72
|
+
acts_as_context
|
73
|
+
attribute :bar
|
74
|
+
end
|
75
|
+
end
|
76
|
+
let(:other_instance) { other_class.new(bar: 'bar') }
|
77
|
+
|
78
|
+
it { expect { subject }.to raise_error(ActiveModel::UnknownAttributeError) }
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: activeinteractor
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.
|
4
|
+
version: 1.0.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Aaron Allen
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-
|
11
|
+
date: 2020-02-04 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activemodel
|
@@ -127,6 +127,8 @@ files:
|
|
127
127
|
- lib/active_interactor/organizer/perform.rb
|
128
128
|
- lib/active_interactor/rails.rb
|
129
129
|
- lib/active_interactor/rails/orm/active_record.rb
|
130
|
+
- lib/active_interactor/rails/orm/dynamoid.rb
|
131
|
+
- lib/active_interactor/rails/orm/mongoid.rb
|
130
132
|
- lib/active_interactor/rails/railtie.rb
|
131
133
|
- lib/active_interactor/version.rb
|
132
134
|
- lib/rails/generators/active_interactor.rb
|
@@ -186,12 +188,8 @@ files:
|
|
186
188
|
- spec/integration/an_organizer_with_before_each_callbacks_spec.rb
|
187
189
|
- spec/integration/an_organizer_with_conditionally_organized_interactors_spec.rb
|
188
190
|
- spec/spec_helper.rb
|
189
|
-
- spec/support/coverage.rb
|
190
|
-
- spec/support/coverage/reporters.rb
|
191
|
-
- spec/support/coverage/reporters/codacy.rb
|
192
|
-
- spec/support/coverage/reporters/simple_cov.rb
|
193
|
-
- spec/support/coverage/runner.rb
|
194
191
|
- spec/support/helpers/factories.rb
|
192
|
+
- spec/support/shared_examples/a_class_that_extends_active_interactor_models_example.rb
|
195
193
|
- spec/support/shared_examples/a_class_with_interactor_callback_methods_example.rb
|
196
194
|
- spec/support/shared_examples/a_class_with_interactor_context_methods_example.rb
|
197
195
|
- spec/support/shared_examples/a_class_with_interactor_methods_example.rb
|
@@ -202,10 +200,10 @@ licenses:
|
|
202
200
|
- MIT
|
203
201
|
metadata:
|
204
202
|
bug_tracker_uri: https://github.com/aaronmallen/activeinteractor/issues
|
205
|
-
changelog_uri: https://github.com/aaronmallen/activeinteractor/blob/v1.0.
|
206
|
-
documentation_uri: https://www.rubydoc.info/gems/activeinteractor/1.0.
|
203
|
+
changelog_uri: https://github.com/aaronmallen/activeinteractor/blob/v1.0.2/CHANGELOG.md
|
204
|
+
documentation_uri: https://www.rubydoc.info/gems/activeinteractor/1.0.2
|
207
205
|
hompage_uri: https://github.com/aaronmallen/activeinteractor
|
208
|
-
source_code_uri: https://github.com/aaronmallen/activeinteractor/tree/v1.0.
|
206
|
+
source_code_uri: https://github.com/aaronmallen/activeinteractor/tree/v1.0.2
|
209
207
|
wiki_uri: https://github.com/aaronmallen/activeinteractor/wiki
|
210
208
|
post_install_message:
|
211
209
|
rdoc_options: []
|
@@ -227,45 +225,41 @@ signing_key:
|
|
227
225
|
specification_version: 4
|
228
226
|
summary: Ruby interactors with ActiveModel::Validations
|
229
227
|
test_files:
|
230
|
-
- spec/
|
231
|
-
- spec/
|
232
|
-
- spec/
|
233
|
-
- spec/
|
234
|
-
- spec/
|
235
|
-
- spec/
|
236
|
-
- spec/
|
237
|
-
- spec/
|
238
|
-
- spec/
|
228
|
+
- spec/spec_helper.rb
|
229
|
+
- spec/support/helpers/factories.rb
|
230
|
+
- spec/support/spec_helpers.rb
|
231
|
+
- spec/support/shared_examples/a_class_with_interactor_context_methods_example.rb
|
232
|
+
- spec/support/shared_examples/a_class_with_interactor_methods_example.rb
|
233
|
+
- spec/support/shared_examples/a_class_that_extends_active_interactor_models_example.rb
|
234
|
+
- spec/support/shared_examples/a_class_with_organizer_callback_methods_example.rb
|
235
|
+
- spec/support/shared_examples/a_class_with_interactor_callback_methods_example.rb
|
236
|
+
- spec/active_interactor/interactor/perform/options_spec.rb
|
237
|
+
- spec/active_interactor/interactor/worker_spec.rb
|
238
|
+
- spec/active_interactor/error_spec.rb
|
239
|
+
- spec/active_interactor/config_spec.rb
|
240
|
+
- spec/active_interactor/organizer/interactor_interface_collection_spec.rb
|
241
|
+
- spec/active_interactor/organizer/interactor_interface_spec.rb
|
242
|
+
- spec/active_interactor/organizer/base_spec.rb
|
243
|
+
- spec/active_interactor/base_spec.rb
|
244
|
+
- spec/active_interactor/context/base_spec.rb
|
239
245
|
- spec/integration/an_organizer_performing_in_parallel_spec.rb
|
240
|
-
- spec/integration/an_interactor_with_an_existing_context_class_spec.rb
|
241
|
-
- spec/integration/an_interactor_with_after_rollback_callbacks_spec.rb
|
242
|
-
- spec/integration/an_interactor_with_validations_spec.rb
|
243
246
|
- spec/integration/a_basic_interactor_spec.rb
|
244
|
-
- spec/integration/
|
247
|
+
- spec/integration/an_interactor_with_an_existing_context_class_spec.rb
|
248
|
+
- spec/integration/a_failing_interactor_spec.rb
|
245
249
|
- spec/integration/an_interactor_with_before_perform_callbacks_spec.rb
|
246
|
-
- spec/integration/an_organizer_with_before_each_callbacks_spec.rb
|
247
250
|
- spec/integration/an_organizer_with_conditionally_organized_interactors_spec.rb
|
251
|
+
- spec/integration/an_interactor_with_validations_spec.rb
|
252
|
+
- spec/integration/an_organizer_with_before_each_callbacks_spec.rb
|
253
|
+
- spec/integration/an_interactor_with_after_rollback_callbacks_spec.rb
|
248
254
|
- spec/integration/an_organizer_with_around_each_callbacks_spec.rb
|
249
|
-
- spec/integration/
|
255
|
+
- spec/integration/an_interactor_with_before_rollback_callbacks_spec.rb
|
256
|
+
- spec/integration/an_interactor_with_validations_on_calling_spec.rb
|
257
|
+
- spec/integration/an_organizer_with_after_each_callbacks_spec.rb
|
258
|
+
- spec/integration/a_basic_organizer_spec.rb
|
250
259
|
- spec/integration/an_interactor_with_after_perform_callbacks_spec.rb
|
251
|
-
- spec/
|
252
|
-
- spec/
|
253
|
-
- spec/
|
254
|
-
- spec/
|
255
|
-
- spec/
|
256
|
-
- spec/
|
257
|
-
- spec/support/shared_examples/a_class_with_interactor_callback_methods_example.rb
|
258
|
-
- spec/support/shared_examples/a_class_with_interactor_methods_example.rb
|
259
|
-
- spec/support/shared_examples/a_class_with_interactor_context_methods_example.rb
|
260
|
-
- spec/support/helpers/factories.rb
|
261
|
-
- spec/support/coverage.rb
|
262
|
-
- spec/spec_helper.rb
|
263
|
-
- spec/active_interactor/context/base_spec.rb
|
264
|
-
- spec/active_interactor/base_spec.rb
|
265
|
-
- spec/active_interactor/config_spec.rb
|
266
|
-
- spec/active_interactor/organizer/base_spec.rb
|
267
|
-
- spec/active_interactor/organizer/interactor_interface_spec.rb
|
268
|
-
- spec/active_interactor/organizer/interactor_interface_collection_spec.rb
|
269
|
-
- spec/active_interactor/interactor/worker_spec.rb
|
270
|
-
- spec/active_interactor/interactor/perform/options_spec.rb
|
271
|
-
- spec/active_interactor/error_spec.rb
|
260
|
+
- spec/integration/an_interactor_with_around_rollback_callbacks_spec.rb
|
261
|
+
- spec/integration/an_interactor_with_around_perform_callbacks_spec.rb
|
262
|
+
- spec/integration/an_interactor_with_validations_on_called_spec.rb
|
263
|
+
- spec/integration/an_interactor_with_after_context_validation_callbacks_spec.rb
|
264
|
+
- spec/integration/active_record_integration_spec.rb
|
265
|
+
- spec/active_interactor_spec.rb
|
data/spec/support/coverage.rb
DELETED
@@ -1,39 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Spec
|
4
|
-
module Coverage
|
5
|
-
module Reporters
|
6
|
-
module Codacy
|
7
|
-
class << self
|
8
|
-
def load
|
9
|
-
return EMPTY_LOAD_RESULT unless load_dependencies
|
10
|
-
|
11
|
-
[formatters, initialize_steps, run_steps]
|
12
|
-
end
|
13
|
-
|
14
|
-
private
|
15
|
-
|
16
|
-
def load_dependencies
|
17
|
-
require 'codacy-coverage'
|
18
|
-
true
|
19
|
-
rescue LoadError
|
20
|
-
puts 'Skipping Codacy Coverage reporting...'
|
21
|
-
false
|
22
|
-
end
|
23
|
-
|
24
|
-
def formatters
|
25
|
-
{ codacy: ::Codacy::Formatter }
|
26
|
-
end
|
27
|
-
|
28
|
-
def initialize_steps
|
29
|
-
[]
|
30
|
-
end
|
31
|
-
|
32
|
-
def run_steps
|
33
|
-
[]
|
34
|
-
end
|
35
|
-
end
|
36
|
-
end
|
37
|
-
end
|
38
|
-
end
|
39
|
-
end
|
@@ -1,54 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Spec
|
4
|
-
module Coverage
|
5
|
-
module Reporters
|
6
|
-
module SimpleCov
|
7
|
-
class << self
|
8
|
-
def load
|
9
|
-
return EMPTY_LOAD_RESULT unless load_dependencies
|
10
|
-
|
11
|
-
[formatters, initialize_steps, run_steps]
|
12
|
-
end
|
13
|
-
|
14
|
-
private
|
15
|
-
|
16
|
-
def load_dependencies
|
17
|
-
require 'simplecov'
|
18
|
-
true
|
19
|
-
rescue LoadError
|
20
|
-
puts 'Skipping SimpleCov reporting...'
|
21
|
-
false
|
22
|
-
end
|
23
|
-
|
24
|
-
def formatters
|
25
|
-
{ simple_cov_html: ::SimpleCov::Formatter::HTMLFormatter }
|
26
|
-
end
|
27
|
-
|
28
|
-
def initialize_steps
|
29
|
-
[setup_formatters]
|
30
|
-
end
|
31
|
-
|
32
|
-
def run_steps
|
33
|
-
[start_simplecov]
|
34
|
-
end
|
35
|
-
|
36
|
-
def setup_formatters
|
37
|
-
lambda do |runner|
|
38
|
-
::SimpleCov.formatter = ::SimpleCov::Formatter::MultiFormatter.new(runner.formatters)
|
39
|
-
end
|
40
|
-
end
|
41
|
-
|
42
|
-
def start_simplecov
|
43
|
-
lambda do |runner|
|
44
|
-
::SimpleCov.start do
|
45
|
-
runner.tracked_files.each { |f| track_files f }
|
46
|
-
runner.filtered_files.each { |f| add_filter f }
|
47
|
-
end
|
48
|
-
end
|
49
|
-
end
|
50
|
-
end
|
51
|
-
end
|
52
|
-
end
|
53
|
-
end
|
54
|
-
end
|
@@ -1,66 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Spec
|
4
|
-
module Coverage
|
5
|
-
class Runner
|
6
|
-
attr_accessor :filtered_files, :formatters, :tracked_files
|
7
|
-
|
8
|
-
def self.start(&block)
|
9
|
-
instance = new
|
10
|
-
instance.instance_eval(&block) if block
|
11
|
-
instance.start
|
12
|
-
end
|
13
|
-
|
14
|
-
def initialize(options = {})
|
15
|
-
load_reporters!
|
16
|
-
@formatters = options[:formatters] || []
|
17
|
-
@filtered_files = options[:filtered_files] || []
|
18
|
-
@tracked_files = options[:tracked_files] || []
|
19
|
-
end
|
20
|
-
|
21
|
-
def add_formatter(formatter)
|
22
|
-
formatter_class = reporters[:formatters][formatter.to_sym]
|
23
|
-
formatters << formatter_class if formatter_class
|
24
|
-
self
|
25
|
-
end
|
26
|
-
|
27
|
-
def filter(pattern)
|
28
|
-
filtered_files << pattern
|
29
|
-
self
|
30
|
-
end
|
31
|
-
|
32
|
-
def start
|
33
|
-
initialize_reporters
|
34
|
-
run_reporters
|
35
|
-
end
|
36
|
-
|
37
|
-
def track(pattern)
|
38
|
-
tracked_files << pattern
|
39
|
-
self
|
40
|
-
end
|
41
|
-
|
42
|
-
private
|
43
|
-
|
44
|
-
attr_reader :reporters
|
45
|
-
|
46
|
-
def initialize_reporters
|
47
|
-
reporters[:initialize_steps].each { |step| step.call(self) }
|
48
|
-
end
|
49
|
-
|
50
|
-
def load_reporters!
|
51
|
-
@reporters = { formatters: {}, initialize_steps: [], run_steps: [] }
|
52
|
-
%w[SimpleCov Codacy].each do |dep|
|
53
|
-
reporter = Reporters.const_get(dep)
|
54
|
-
formatters, initialize_steps, run_steps = reporter.load
|
55
|
-
@reporters[:formatters].merge!(formatters)
|
56
|
-
@reporters[:initialize_steps].concat(initialize_steps)
|
57
|
-
@reporters[:run_steps].concat(run_steps)
|
58
|
-
end
|
59
|
-
end
|
60
|
-
|
61
|
-
def run_reporters
|
62
|
-
reporters[:run_steps].each { |step| step.call(self) }
|
63
|
-
end
|
64
|
-
end
|
65
|
-
end
|
66
|
-
end
|