activeinteractor 1.0.0.beta.2 → 1.0.0.beta.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +17 -1
- data/README.md +40 -4
- data/lib/active_interactor/config.rb +5 -11
- data/lib/active_interactor/configurable.rb +45 -0
- data/lib/active_interactor/context/attributes.rb +0 -17
- data/lib/active_interactor/context/base.rb +52 -0
- data/lib/active_interactor/interactor.rb +9 -7
- data/lib/active_interactor/interactor/perform_options.rb +45 -0
- data/lib/active_interactor/interactor/worker.rb +68 -18
- data/lib/active_interactor/organizer.rb +40 -8
- data/lib/active_interactor/rails/config.rb +5 -12
- data/lib/active_interactor/version.rb +1 -1
- data/spec/active_interactor/config_spec.rb +8 -0
- data/spec/active_interactor/context/base_spec.rb +122 -13
- data/spec/active_interactor/interactor/worker_spec.rb +99 -0
- data/spec/active_interactor/organizer_spec.rb +67 -0
- data/spec/active_interactor/rails/config_spec.rb +12 -0
- data/spec/integration/basic_integration_spec.rb +51 -0
- metadata +7 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ec758d003a313ede93fb1abbabc42cae23e071b9c675a9515006a5fb0e3a02a4
|
4
|
+
data.tar.gz: 6e1c83a0e0f7a37f63a9d9523cfbad34ccd8fb1996c5329e91294c772f4898bc
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 91ccd10ef1bd421dcf3275a980fe279de279c60a52b4815c0f4ae8b44c6d4721ed135f2e3978dd1c6cca4bc1acc3fd064013023ac89e48a1fd4fb53ba4c32f7d
|
7
|
+
data.tar.gz: fec0e7ab4a5976efae4b0c64cc4593789b914d8c6208966575af9f79b6ebbd495d30c2c9e5ef942a060334620b6dfca946c67c27a694a36295bf194d41cd4887
|
data/CHANGELOG.md
CHANGED
@@ -7,6 +7,19 @@ and this project adheres to [Semantic Versioning].
|
|
7
7
|
|
8
8
|
## [Unreleased]
|
9
9
|
|
10
|
+
## [v1.0.0-beta.3] - 2020-01-12
|
11
|
+
|
12
|
+
### Added
|
13
|
+
|
14
|
+
- [#109] `ActiveInteractor::Organizer.parallel`
|
15
|
+
- [#109] `ActiveInteractor::Organizer.perform_in_parallel`
|
16
|
+
- [#109] `ActiveInteractor::Context::Base#merge`
|
17
|
+
- [#110] `ActiveInteractor::Interactor::PerformOptions`
|
18
|
+
|
19
|
+
### Changed
|
20
|
+
|
21
|
+
- [#110] `ActiveInteractor::Interactor.perform` now takes options
|
22
|
+
|
10
23
|
## [v1.0.0-beta.2] - 2020-01-07
|
11
24
|
|
12
25
|
### Added
|
@@ -134,7 +147,8 @@ and this project adheres to [Semantic Versioning].
|
|
134
147
|
|
135
148
|
<!-- versions -->
|
136
149
|
|
137
|
-
[Unreleased]: https://github.com/aaronmallen/activeinteractor/compare/v1.0.0-beta.
|
150
|
+
[Unreleased]: https://github.com/aaronmallen/activeinteractor/compare/v1.0.0-beta.3...HEAD
|
151
|
+
[v1.0.0-beta.3]: https://github.com/aaronmallen/activeinteractor/compare/v1.0.0-beta.2...v1.0.0-beta.3
|
138
152
|
[v1.0.0-beta.2]: https://github.com/aaronmallen/activeinteractor/compare/v1.0.0-beta.1...v1.0.0-beta.2
|
139
153
|
[v1.0.0-beta.1]: https://github.com/aaronmallen/activeinteractor/compare/v0.1.7...v1.0.0-beta.1
|
140
154
|
[v0.1.7]: https://github.com/aaronmallen/activeinteractor/compare/v0.1.6...v0.1.7
|
@@ -165,3 +179,5 @@ and this project adheres to [Semantic Versioning].
|
|
165
179
|
[#103]: https://github.com/aaronmallen/activeinteractor/pull/103
|
166
180
|
[#104]: https://github.com/aaronmallen/activeinteractor/pull/104
|
167
181
|
[#105]: https://github.com/aaronmallen/activeinteractor/pull/105
|
182
|
+
[#109]: https://github.com/aaronmallen/activeinteractor/pull/109
|
183
|
+
[#110]: https://github.com/aaronmallen/activeinteractor/pull/110
|
data/README.md
CHANGED
@@ -24,8 +24,9 @@ Ruby interactors with [ActiveModel::Validations] based on the [interactor][colle
|
|
24
24
|
* [Validating the Context](#validating-the-context)
|
25
25
|
* [Using Interactors](#using-interactors)
|
26
26
|
* [Kinds of Interactors](#kinds-of-interactors)
|
27
|
-
|
28
|
-
|
27
|
+
* [Interactors](#interactors)
|
28
|
+
* [Organizers](#organizers)
|
29
|
+
* [Parallel Organizers](#parallel-organizers)
|
29
30
|
* [Rollback](#rollback)
|
30
31
|
* [Callbacks](#callbacks)
|
31
32
|
* [Validation Callbacks](#validation-callbacks)
|
@@ -346,7 +347,7 @@ Finally, the context (along with any changes made to it) is returned.
|
|
346
347
|
|
347
348
|
There are two kinds of interactors built into the Interactor library: basic interactors and organizers.
|
348
349
|
|
349
|
-
|
350
|
+
##### Interactors
|
350
351
|
|
351
352
|
A basic interactor is a class that includes Interactor and defines `perform`.\
|
352
353
|
|
@@ -366,7 +367,7 @@ end
|
|
366
367
|
|
367
368
|
Basic interactors are the building blocks. They are your application's single-purpose units of work.
|
368
369
|
|
369
|
-
|
370
|
+
##### Organizers
|
370
371
|
|
371
372
|
An organizer is an important variation on the basic interactor. Its single purpose is to run other interactors.
|
372
373
|
|
@@ -421,6 +422,41 @@ end
|
|
421
422
|
The organizer passes its context to the interactors that it organizes, one at a time and in order. Each interactor may
|
422
423
|
change that context before it's passed along to the next interactor.
|
423
424
|
|
425
|
+
##### Parallel Organizers
|
426
|
+
|
427
|
+
Organizers can be told to run their interactors in parallel with the `#perform_in_parallel` class method. This
|
428
|
+
will run each interactor in parallel with one and other only passing the original context to each organizer.
|
429
|
+
This means each interactor must be able to perform without dependencies on prior interactor runs.
|
430
|
+
|
431
|
+
```ruby
|
432
|
+
class CreateNewUser < ActiveInteractor::Base
|
433
|
+
def perform
|
434
|
+
context.user = User.create(
|
435
|
+
first_name: context.first_name,
|
436
|
+
last_name: context.last_name
|
437
|
+
)
|
438
|
+
end
|
439
|
+
end
|
440
|
+
|
441
|
+
class LogNewUserCreation < ActiveInteractor::Base
|
442
|
+
def perform
|
443
|
+
context.log = Log.create(
|
444
|
+
event: 'new user created',
|
445
|
+
first_name: context.first_name,
|
446
|
+
last_name: context.last_name
|
447
|
+
)
|
448
|
+
end
|
449
|
+
end
|
450
|
+
|
451
|
+
class CreateUser < ActiveInteractor::Organizer
|
452
|
+
perform_in_parallel
|
453
|
+
organize :create_new_user, :log_new_user_creation
|
454
|
+
end
|
455
|
+
|
456
|
+
CreateUser.perform(first_name: 'Aaron', last_name: 'Allen')
|
457
|
+
#=> <#CreateUser::Context first_name='Aaron' last_name='Allen' user=>#<User ...> log=<#Log ...>>
|
458
|
+
```
|
459
|
+
|
424
460
|
#### Rollback
|
425
461
|
|
426
462
|
If any one of the organized interactors fails its context, the organizer stops. If the `ChargeCard` interactor fails,
|
@@ -2,6 +2,8 @@
|
|
2
2
|
|
3
3
|
require 'logger'
|
4
4
|
|
5
|
+
require 'active_interactor/configurable'
|
6
|
+
|
5
7
|
module ActiveInteractor
|
6
8
|
# The ActiveInteractor configuration object
|
7
9
|
# @author Aaron Allen <hello@aaronmallen.me>
|
@@ -9,21 +11,13 @@ module ActiveInteractor
|
|
9
11
|
# @!attribute [rw] logger
|
10
12
|
# @return [Logger] an instance of Logger
|
11
13
|
class Config
|
12
|
-
|
13
|
-
|
14
|
-
logger: Logger.new(STDOUT)
|
15
|
-
}.freeze
|
16
|
-
|
17
|
-
attr_accessor :logger
|
14
|
+
include Configurable
|
15
|
+
defaults logger: Logger.new(STDOUT)
|
18
16
|
|
17
|
+
# @!method initialize(options = {})
|
19
18
|
# @param options [Hash] the options for the configuration
|
20
19
|
# @option options [Logger] :logger the configuration logger instance
|
21
20
|
# @return [Config] a new instance of {Config}
|
22
|
-
def initialize(options = {})
|
23
|
-
DEFAULTS.dup.merge(options).each do |key, value|
|
24
|
-
instance_variable_set("@#{key}", value)
|
25
|
-
end
|
26
|
-
end
|
27
21
|
|
28
22
|
# The rails configuration object
|
29
23
|
# @return [Rails::Config|nil] an instance of {Rails::Config} if `Rails` is
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'active_support/core_ext/class/attribute'
|
4
|
+
|
5
|
+
module ActiveInteractor
|
6
|
+
# Methods for configurable objects
|
7
|
+
# @author Aaron Allen <hello@aaronmallen.me>
|
8
|
+
# @since 1.0.0
|
9
|
+
module Configurable
|
10
|
+
def self.included(base)
|
11
|
+
base.class_eval do
|
12
|
+
extend ClassMethods
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
# Class methods for configuable objects
|
17
|
+
module ClassMethods
|
18
|
+
# Set or get default options for a configurable object
|
19
|
+
# @param options [Hash] the options to set for defaults
|
20
|
+
# @return [Hash] the defaults
|
21
|
+
def defaults(options = {})
|
22
|
+
return __defaults if options.empty?
|
23
|
+
|
24
|
+
options.each do |key, value|
|
25
|
+
__defaults[key.to_sym] = value
|
26
|
+
send(:attr_accessor, key.to_sym)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def __defaults
|
33
|
+
@__defaults ||= {}
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
# @param options [Hash] the options for the configuration
|
38
|
+
# @return [Configurable] a new instance of {Configurable}
|
39
|
+
def initialize(options = {})
|
40
|
+
self.class.defaults.merge(options).each do |key, value|
|
41
|
+
instance_variable_set("@#{key}", value)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -36,14 +36,6 @@ module ActiveInteractor
|
|
36
36
|
end
|
37
37
|
end
|
38
38
|
|
39
|
-
# @api private
|
40
|
-
# @param context [Hash|Context::Base] attributes to assign to the context
|
41
|
-
# @return [Context::Base] a new instance of {Context::Base}
|
42
|
-
def initialize(context = {})
|
43
|
-
copy_flags!(context)
|
44
|
-
super
|
45
|
-
end
|
46
|
-
|
47
39
|
# Attributes defined on the instance
|
48
40
|
# @example Get attributes defined on an instance
|
49
41
|
# class MyInteractor::Context < ActiveInteractor::Context::Base
|
@@ -65,15 +57,6 @@ module ActiveInteractor
|
|
65
57
|
hash[attribute] = self[attribute] if self[attribute]
|
66
58
|
end
|
67
59
|
end
|
68
|
-
|
69
|
-
private
|
70
|
-
|
71
|
-
def copy_flags!(context)
|
72
|
-
%w[_called _failed _rolled_back].each do |flag|
|
73
|
-
value = context.instance_variable_get("@#{flag}")
|
74
|
-
instance_variable_set("@#{flag}", value)
|
75
|
-
end
|
76
|
-
end
|
77
60
|
end
|
78
61
|
end
|
79
62
|
end
|
@@ -15,6 +15,15 @@ module ActiveInteractor
|
|
15
15
|
include ActiveModel::Validations
|
16
16
|
include Attributes
|
17
17
|
|
18
|
+
# @param context [Hash|Context::Base] attributes to assign to the context
|
19
|
+
# @return [Context::Base] a new instance of {Context::Base}
|
20
|
+
def initialize(context = {})
|
21
|
+
merge_errors!(context.errors) if context.respond_to?(:errors)
|
22
|
+
copy_flags!(context)
|
23
|
+
copy_called!(context)
|
24
|
+
super
|
25
|
+
end
|
26
|
+
|
18
27
|
# @!method valid?(context = nil)
|
19
28
|
# @see
|
20
29
|
# https://github.com/rails/rails/blob/master/activemodel/lib/active_model/validations.rb#L305
|
@@ -72,6 +81,37 @@ module ActiveInteractor
|
|
72
81
|
end
|
73
82
|
alias fail? failure?
|
74
83
|
|
84
|
+
# Merge an instance of context or a hash into an existing context
|
85
|
+
# @since 1.0.0
|
86
|
+
# @example
|
87
|
+
# class MyInteractor1 < ActiveInteractor::Base
|
88
|
+
# def perform
|
89
|
+
# context.first_name = 'Aaron'
|
90
|
+
# end
|
91
|
+
# end
|
92
|
+
#
|
93
|
+
# class MyInteractor2 < ActiveInteractor::Base
|
94
|
+
# def perform
|
95
|
+
# context.last_name = 'Allen'
|
96
|
+
# end
|
97
|
+
# end
|
98
|
+
#
|
99
|
+
# result = MyInteractor1.perform
|
100
|
+
# #=> <#MyInteractor1::Context first_name='Aaron'>
|
101
|
+
#
|
102
|
+
# result.merge!(MyInteractor2.perform)
|
103
|
+
# #=> <#MyInteractor1::Context first_name='Aaron' last_name='Allen'>
|
104
|
+
# @param context [Base|Hash] attributes to merge into the context
|
105
|
+
# @return [Base] an instance of {Base}
|
106
|
+
def merge!(context)
|
107
|
+
merge_errors!(context.errors) if context.respond_to?(:errors)
|
108
|
+
copy_flags!(context)
|
109
|
+
context.each_pair do |key, value|
|
110
|
+
self[key] = value
|
111
|
+
end
|
112
|
+
self
|
113
|
+
end
|
114
|
+
|
75
115
|
# Roll back an interactor context. Any interactors to which this
|
76
116
|
# context has been passed and which have been successfully called are asked
|
77
117
|
# to roll themselves back by invoking their
|
@@ -129,6 +169,18 @@ module ActiveInteractor
|
|
129
169
|
@_called ||= []
|
130
170
|
end
|
131
171
|
|
172
|
+
def copy_called!(context)
|
173
|
+
value = context.instance_variable_get('@_called') || []
|
174
|
+
instance_variable_set('@_called', value)
|
175
|
+
end
|
176
|
+
|
177
|
+
def copy_flags!(context)
|
178
|
+
%w[_failed _rolled_back].each do |flag|
|
179
|
+
value = context.instance_variable_get("@#{flag}")
|
180
|
+
instance_variable_set("@#{flag}", value)
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
132
184
|
def merge_errors!(errors)
|
133
185
|
if errors.is_a? String
|
134
186
|
self.errors.add(:context, errors)
|
@@ -14,6 +14,7 @@ module ActiveInteractor
|
|
14
14
|
delegate :execute_perform, :execute_perform!, :execute_rollback, to: :worker
|
15
15
|
end
|
16
16
|
end
|
17
|
+
|
17
18
|
# Interactor class methods.
|
18
19
|
module ClassMethods
|
19
20
|
# Run an interactor context. This it the primary API method for
|
@@ -21,11 +22,11 @@ module ActiveInteractor
|
|
21
22
|
# @example Run an interactor
|
22
23
|
# MyInteractor.perform(name: 'Aaron')
|
23
24
|
# #=> <#MyInteractor::Context name='Aaron'>
|
24
|
-
# @param context [Hash|Context::Base]
|
25
|
-
#
|
25
|
+
# @param context [Hash|Context::Base] attributes to assign to the interactor context
|
26
|
+
# @param options [PerformOptions|Hash] execution options for the interactor perform step
|
26
27
|
# @return [Context::Base] an instance of context.
|
27
|
-
def perform(context = {})
|
28
|
-
new(context).execute_perform
|
28
|
+
def perform(context = {}, options = PerformOptions.new)
|
29
|
+
new(context).execute_perform(options)
|
29
30
|
end
|
30
31
|
|
31
32
|
# Run an interactor context. The {.perform!} method behaves identically to
|
@@ -35,11 +36,12 @@ module ActiveInteractor
|
|
35
36
|
# @example Run an interactor
|
36
37
|
# MyInteractor.perform!(name: 'Aaron')
|
37
38
|
# #=> <#MyInteractor::Context name='Aaron'>
|
38
|
-
# @param context [Hash|Context::Base]
|
39
|
+
# @param context [Hash|Context::Base] attributes to assign to the interactor context
|
40
|
+
# @param options [PerformOptions|Hash] execution options for the interactor perform step
|
39
41
|
# @raise [Error::ContextFailure] if the context fails.
|
40
42
|
# @return [Context::Base] an instance of context.
|
41
|
-
def perform!(context = {})
|
42
|
-
new(context).execute_perform!
|
43
|
+
def perform!(context = {}, options = PerformOptions.new)
|
44
|
+
new(context).execute_perform!(options)
|
43
45
|
end
|
44
46
|
end
|
45
47
|
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveInteractor
|
4
|
+
module Interactor
|
5
|
+
# Options object for interactor perform
|
6
|
+
# @author Aaron Allen <hello@aaronmallen.me>
|
7
|
+
# @since 1.0.0
|
8
|
+
# @!attribute [rw] skip_perform_callbacks
|
9
|
+
# @return [Boolean] whether or not to skip :perform callbacks
|
10
|
+
# @!attribute [rw] skip_rollback
|
11
|
+
# @return [Boolean] whether or not to skip rollback when an interactor
|
12
|
+
# fails its context
|
13
|
+
# @!attribute [rw] skip_rollback_callbacks
|
14
|
+
# @return [Boolean] whether or not to skip :rollback callbacks
|
15
|
+
# @!attribute [rw] validate
|
16
|
+
# @return [Boolean] whether or not to run validations on an interactor
|
17
|
+
# @!attribute [rw] validate_on_calling
|
18
|
+
# @return [Boolean] whether or not to run validation on :calling
|
19
|
+
# @!attribute [rw] validate_on_called
|
20
|
+
# @return [Boolean] whether or not to run validation on :called
|
21
|
+
class PerformOptions
|
22
|
+
# Default options for interactor perform
|
23
|
+
# @return [Hash{Symbol=>*}] the default options
|
24
|
+
DEFAULTS = {
|
25
|
+
skip_perform_callbacks: false,
|
26
|
+
skip_rollback: false,
|
27
|
+
skip_rollback_callbacks: false,
|
28
|
+
validate: true,
|
29
|
+
validate_on_calling: true,
|
30
|
+
validate_on_called: true
|
31
|
+
}.freeze
|
32
|
+
|
33
|
+
attr_accessor :skip_perform_callbacks, :skip_rollback, :skip_rollback_callbacks,
|
34
|
+
:validate, :validate_on_calling, :validate_on_called
|
35
|
+
|
36
|
+
# @param options [Hash] the attributes for the {PerformOptions}
|
37
|
+
# @return [PerformOptions] a new instance of {PerformOptions}
|
38
|
+
def initialize(options = {})
|
39
|
+
DEFAULTS.dup.merge(options).each do |key, value|
|
40
|
+
instance_variable_set("@#{key}", value)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -17,45 +17,83 @@ module ActiveInteractor
|
|
17
17
|
end
|
18
18
|
|
19
19
|
# Calls {#execute_perform!} and rescues {Error::ContextFailure}
|
20
|
+
# @param options [PerformOptions|Hash] execution options for the interactor perform step
|
20
21
|
# @return [Context::Base] an instance of {Context::Base}
|
21
|
-
def execute_perform
|
22
|
-
execute_perform!
|
22
|
+
def execute_perform(options = PerformOptions.new)
|
23
|
+
execute_perform!(options)
|
23
24
|
rescue Error::ContextFailure => e
|
24
25
|
ActiveInteractor.logger.error("ActiveInteractor: #{e}")
|
25
26
|
context
|
26
27
|
end
|
27
28
|
|
28
29
|
# Calls {Interactor#perform} with callbacks and context validation
|
30
|
+
# @param options [PerformOptions|Hash] execution options for the interactor perform step
|
29
31
|
# @raise [Error::ContextFailure] if the context fails
|
30
32
|
# @return [Context::Base] an instance of {Context::Base}
|
31
|
-
def execute_perform!
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
@context = interactor.finalize_context!
|
37
|
-
execute_rollback
|
38
|
-
raise
|
39
|
-
end
|
33
|
+
def execute_perform!(options = PerformOptions.new)
|
34
|
+
options = parse_options(options)
|
35
|
+
execute_context!(options)
|
36
|
+
rescue StandardError => e
|
37
|
+
handle_error(e, options)
|
40
38
|
end
|
41
39
|
|
42
40
|
# Calls {Interactor#rollback} with callbacks
|
43
41
|
# @return [Boolean] `true` if rolled back successfully or `false` if already
|
44
42
|
# rolled back
|
45
|
-
def execute_rollback
|
46
|
-
|
47
|
-
|
48
|
-
|
43
|
+
def execute_rollback(options = PerformOptions.new)
|
44
|
+
options = parse_options(options)
|
45
|
+
return if options.skip_rollback
|
46
|
+
|
47
|
+
execute_interactor_rollback!(options)
|
49
48
|
end
|
50
49
|
|
51
50
|
private
|
52
51
|
|
53
52
|
attr_reader :context, :interactor
|
54
53
|
|
55
|
-
def execute_context!
|
56
|
-
|
54
|
+
def execute_context!(options)
|
55
|
+
if options.skip_perform_callbacks
|
56
|
+
execute_context_with_validation_check!(options)
|
57
|
+
else
|
58
|
+
execute_context_with_callbacks!(options)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def execute_context_with_callbacks!(options)
|
63
|
+
run_callbacks :perform do
|
64
|
+
execute_context_with_validation_check!(options)
|
65
|
+
@context = interactor.finalize_context!
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def execute_context_with_validation!(options)
|
70
|
+
validate_on_calling(options)
|
57
71
|
interactor.perform
|
58
|
-
|
72
|
+
validate_on_called(options)
|
73
|
+
end
|
74
|
+
|
75
|
+
def execute_context_with_validation_check!(options)
|
76
|
+
return interactor.perform unless options.validate
|
77
|
+
|
78
|
+
execute_context_with_validation!(options)
|
79
|
+
end
|
80
|
+
|
81
|
+
def execute_interactor_rollback!(options)
|
82
|
+
return interactor.context_rollback! if options.skip_rollback_callbacks
|
83
|
+
|
84
|
+
run_callbacks :rollback do
|
85
|
+
interactor.context_rollback!
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def handle_error(exception, options)
|
90
|
+
@context = interactor.finalize_context!
|
91
|
+
execute_rollback(options)
|
92
|
+
raise exception
|
93
|
+
end
|
94
|
+
|
95
|
+
def parse_options(options)
|
96
|
+
@options = options.is_a?(PerformOptions) ? options : PerformOptions.new(options)
|
59
97
|
end
|
60
98
|
|
61
99
|
def validate_context(validation_context = nil)
|
@@ -63,6 +101,18 @@ module ActiveInteractor
|
|
63
101
|
interactor.context_valid?(validation_context)
|
64
102
|
end
|
65
103
|
end
|
104
|
+
|
105
|
+
def validate_on_calling(options)
|
106
|
+
return unless options.validate_on_calling
|
107
|
+
|
108
|
+
interactor.context_fail! unless validate_context(:calling)
|
109
|
+
end
|
110
|
+
|
111
|
+
def validate_on_called(options)
|
112
|
+
return unless options.validate_on_called
|
113
|
+
|
114
|
+
interactor.context_fail! unless validate_context(:called)
|
115
|
+
end
|
66
116
|
end
|
67
117
|
end
|
68
118
|
end
|
@@ -11,6 +11,11 @@ module ActiveInteractor
|
|
11
11
|
# @!attribute [r] organized
|
12
12
|
# @!scope class
|
13
13
|
# @return [Array<Base>] the organized interactors
|
14
|
+
# @!attribute [r] parallel
|
15
|
+
# @since 1.0.0
|
16
|
+
# @!scope class
|
17
|
+
# @return [Boolean] whether or not to run the interactors
|
18
|
+
# in parallel
|
14
19
|
# @example a basic organizer
|
15
20
|
# class MyInteractor1 < ActiveInteractor::Base
|
16
21
|
# def perform
|
@@ -32,6 +37,7 @@ module ActiveInteractor
|
|
32
37
|
# #=> <MyOrganizer::Context interactor1=true interactor2=true>
|
33
38
|
class Organizer < Base
|
34
39
|
class_attribute :organized, instance_writer: false, default: []
|
40
|
+
class_attribute :parallel, instance_writer: false, default: false
|
35
41
|
define_callbacks :each_perform
|
36
42
|
|
37
43
|
# Define a callback to call after each organized interactor's
|
@@ -183,25 +189,51 @@ module ActiveInteractor
|
|
183
189
|
end.compact
|
184
190
|
end
|
185
191
|
|
192
|
+
# Run organized interactors in parallel
|
193
|
+
# @since 1.0.0
|
194
|
+
def self.perform_in_parallel
|
195
|
+
self.parallel = true
|
196
|
+
end
|
197
|
+
|
186
198
|
# Invoke the organized interactors. An organizer is
|
187
199
|
# expected not to define its own {Base#perform} method
|
188
200
|
# in favor of this default implementation.
|
189
201
|
def perform
|
190
|
-
self.class.
|
191
|
-
|
202
|
+
if self.class.parallel
|
203
|
+
perform_in_parallel
|
204
|
+
else
|
205
|
+
perform_in_order
|
192
206
|
end
|
193
|
-
rescue Error::ContextFailure => e
|
194
|
-
self.context = e.context
|
195
|
-
ensure
|
196
|
-
self.context = self.class.context_class.new(context)
|
197
207
|
end
|
198
208
|
|
199
209
|
private
|
200
210
|
|
201
|
-
def
|
211
|
+
def execute_interactor_with_callbacks(interactor, fail_on_error = false, execute_options = {})
|
202
212
|
run_callbacks :each_perform do
|
203
|
-
interactor.new(context)
|
213
|
+
instance = interactor.new(context)
|
214
|
+
method = fail_on_error ? :execute_perform! : :execute_perform
|
215
|
+
instance.send(method, execute_options)
|
216
|
+
end
|
217
|
+
end
|
218
|
+
|
219
|
+
def merge_contexts(contexts)
|
220
|
+
contexts.each { |context| @context.merge!(context) }
|
221
|
+
context_fail! if contexts.any?(&:failure?)
|
222
|
+
end
|
223
|
+
|
224
|
+
def perform_in_order
|
225
|
+
self.class.organized.each do |interactor|
|
226
|
+
context.merge!(execute_interactor_with_callbacks(interactor, true))
|
227
|
+
end
|
228
|
+
rescue Error::ContextFailure => e
|
229
|
+
context.merge!(e.context)
|
230
|
+
end
|
231
|
+
|
232
|
+
def perform_in_parallel
|
233
|
+
results = self.class.organized.map do |interactor|
|
234
|
+
Thread.new { execute_interactor_with_callbacks(interactor, false, skip_rollback: true) }
|
204
235
|
end
|
236
|
+
merge_contexts(results.map(&:value))
|
205
237
|
end
|
206
238
|
end
|
207
239
|
end
|
@@ -1,5 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'active_interactor/configurable'
|
4
|
+
|
3
5
|
module ActiveInteractor
|
4
6
|
module Rails
|
5
7
|
# The ActiveInteractor rails configuration object
|
@@ -11,24 +13,15 @@ module ActiveInteractor
|
|
11
13
|
# @return [Boolean] whether or not to generate a seperate
|
12
14
|
# context class for an interactor.
|
13
15
|
class Config
|
14
|
-
|
15
|
-
|
16
|
-
directory: 'interactors',
|
17
|
-
generate_context_classes: true
|
18
|
-
}.freeze
|
19
|
-
|
20
|
-
attr_accessor :directory, :generate_context_classes
|
16
|
+
include ActiveInteractor::Configurable
|
17
|
+
defaults directory: 'interactors', generate_context_classes: true
|
21
18
|
|
19
|
+
# @!method initialize(options = {})
|
22
20
|
# @param options [Hash] the options for the configuration
|
23
21
|
# @option options [String] :directory (defaults to: 'interactors') the configuration directory property
|
24
22
|
# @option options [Boolean] :generate_context_classes (defaults to: `true`) the configuration
|
25
23
|
# generate_context_class property.
|
26
24
|
# @return [Config] a new instance of {Config}
|
27
|
-
def initialize(options = {})
|
28
|
-
DEFAULTS.dup.merge(options).each do |key, value|
|
29
|
-
instance_variable_set("@#{key}", value)
|
30
|
-
end
|
31
|
-
end
|
32
25
|
end
|
33
26
|
end
|
34
27
|
end
|
@@ -6,6 +6,14 @@ RSpec.describe ActiveInteractor::Config do
|
|
6
6
|
subject { described_class.new }
|
7
7
|
it { is_expected.to respond_to :logger }
|
8
8
|
|
9
|
+
describe '.defaults' do
|
10
|
+
subject { described_class.defaults }
|
11
|
+
|
12
|
+
it 'is expected to have attributes :logger => Logger.new' do
|
13
|
+
expect(subject[:logger]).to be_a Logger
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
9
17
|
describe '.rails' do
|
10
18
|
subject { described_class.new.rails }
|
11
19
|
|
@@ -133,15 +133,137 @@ RSpec.describe ActiveInteractor::Context::Base do
|
|
133
133
|
end
|
134
134
|
end
|
135
135
|
|
136
|
+
describe '#failure?' do
|
137
|
+
subject { instance.failure? }
|
138
|
+
let(:instance) { described_class.new }
|
139
|
+
|
140
|
+
it { is_expected.to eq false }
|
141
|
+
|
142
|
+
context 'when context has failed' do
|
143
|
+
before { instance.instance_variable_set('@_failed', true) }
|
144
|
+
|
145
|
+
it { is_expected.to eq true }
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
describe '#merge' do
|
150
|
+
subject { instance.merge!(attributes) }
|
151
|
+
|
152
|
+
context 'with an instance having attributes { :foo => "foo"}' do
|
153
|
+
let(:instance) { described_class.new(foo: 'foo') }
|
154
|
+
|
155
|
+
context 'with a hash having attributes { :bar => "bar"}' do
|
156
|
+
let(:attributes) { { bar: 'bar' } }
|
157
|
+
|
158
|
+
it { is_expected.to be_a described_class }
|
159
|
+
it { is_expected.to have_attributes(foo: 'foo', bar: 'bar') }
|
160
|
+
end
|
161
|
+
|
162
|
+
context 'with a hash having attributes { :foo => "foobar"}' do
|
163
|
+
let(:attributes) { { foo: 'foobar' } }
|
164
|
+
|
165
|
+
it { is_expected.to be_a described_class }
|
166
|
+
it { is_expected.to have_attributes(foo: 'foobar') }
|
167
|
+
end
|
168
|
+
|
169
|
+
context 'with a previous instance having attributes { :bar => "bar" }' do
|
170
|
+
let(:attributes) { described_class.new(bar: 'bar') }
|
171
|
+
|
172
|
+
it { is_expected.to be_a described_class }
|
173
|
+
it { is_expected.to have_attributes(foo: 'foo', bar: 'bar') }
|
174
|
+
|
175
|
+
context 'having errors on :foo' do
|
176
|
+
before { attributes.errors.add(:foo, 'invalid') }
|
177
|
+
|
178
|
+
it 'is expected to have errors on :foo' do
|
179
|
+
expect(subject.errors[:foo]).not_to be_nil
|
180
|
+
expect(subject.errors[:foo]).to include 'invalid'
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
context 'having instance variable @_failed equal to true' do
|
185
|
+
before { attributes.instance_variable_set('@_failed', true) }
|
186
|
+
|
187
|
+
it { is_expected.to be_a described_class }
|
188
|
+
it { is_expected.to have_attributes(foo: 'foo') }
|
189
|
+
it 'is expected to preserve @_failed instance variable' do
|
190
|
+
expect(subject.instance_variable_get('@_failed')).to eq true
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
context 'having instance variable @_rolled_back equal to true' do
|
195
|
+
before { attributes.instance_variable_set('@_rolled_back', true) }
|
196
|
+
|
197
|
+
it { is_expected.to be_a described_class }
|
198
|
+
it { is_expected.to have_attributes(foo: 'foo') }
|
199
|
+
it 'is expected to preserve @_rolled_back instance variable' do
|
200
|
+
expect(subject.instance_variable_get('@_rolled_back')).to eq true
|
201
|
+
end
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
context 'with a previous instance having attributes { :foo => "foobar"}' do
|
206
|
+
let(:attributes) { described_class.new(foo: 'foobar') }
|
207
|
+
|
208
|
+
it { is_expected.to be_a described_class }
|
209
|
+
it { is_expected.to have_attributes(foo: 'foobar') }
|
210
|
+
end
|
211
|
+
|
212
|
+
context 'having errors on :foo' do
|
213
|
+
before { instance.errors.add(:foo, 'invalid') }
|
214
|
+
|
215
|
+
context 'with a previous instance having attributes { :bar => "bar" }' do
|
216
|
+
let(:attributes) { described_class.new(bar: 'bar') }
|
217
|
+
|
218
|
+
it 'is expected to have errors on :foo' do
|
219
|
+
expect(subject.errors[:foo]).not_to be_nil
|
220
|
+
expect(subject.errors[:foo]).to include 'invalid'
|
221
|
+
end
|
222
|
+
|
223
|
+
context 'having errors on :bar' do
|
224
|
+
before { attributes.errors.add(:bar, 'invalid') }
|
225
|
+
|
226
|
+
it 'is expected to have errors on :foo' do
|
227
|
+
expect(subject.errors[:foo]).not_to be_nil
|
228
|
+
expect(subject.errors[:foo]).to include 'invalid'
|
229
|
+
end
|
230
|
+
|
231
|
+
it 'is expected to have errors on :bar' do
|
232
|
+
expect(subject.errors[:bar]).not_to be_nil
|
233
|
+
expect(subject.errors[:bar]).to include 'invalid'
|
234
|
+
end
|
235
|
+
end
|
236
|
+
end
|
237
|
+
end
|
238
|
+
end
|
239
|
+
end
|
240
|
+
|
136
241
|
describe '#new' do
|
137
242
|
subject { described_class.new(attributes) }
|
138
243
|
|
244
|
+
context 'with a hash having attributes { :foo => "foo"}' do
|
245
|
+
let(:attributes) { { foo: 'foo' } }
|
246
|
+
|
247
|
+
it { is_expected.to be_a described_class }
|
248
|
+
it { is_expected.to have_attributes(foo: 'foo') }
|
249
|
+
end
|
250
|
+
|
139
251
|
context 'with a previous instance having attributes { :foo => "foo" }' do
|
140
252
|
let(:attributes) { described_class.new(foo: 'foo') }
|
141
253
|
|
142
254
|
it { is_expected.to be_a described_class }
|
143
255
|
it { is_expected.to have_attributes(foo: 'foo') }
|
144
256
|
|
257
|
+
context 'having errors on :foo' do
|
258
|
+
before { attributes.errors.add(:foo, 'invalid') }
|
259
|
+
|
260
|
+
it { is_expected.to be_a described_class }
|
261
|
+
it 'is expected to have errors on :foo' do
|
262
|
+
expect(subject.errors[:foo]).not_to be_nil
|
263
|
+
expect(subject.errors[:foo]).to include 'invalid'
|
264
|
+
end
|
265
|
+
end
|
266
|
+
|
145
267
|
context 'having instance variable @_called equal to ["foo"]' do
|
146
268
|
before { attributes.instance_variable_set('@_called', %w[foo]) }
|
147
269
|
|
@@ -174,19 +296,6 @@ RSpec.describe ActiveInteractor::Context::Base do
|
|
174
296
|
end
|
175
297
|
end
|
176
298
|
|
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
299
|
describe '#rollback!' do
|
191
300
|
subject { instance.rollback! }
|
192
301
|
let(:instance) { described_class.new }
|
@@ -1,6 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'spec_helper'
|
4
|
+
require 'active_interactor/interactor/worker'
|
4
5
|
|
5
6
|
RSpec.describe ActiveInteractor::Interactor::Worker do
|
6
7
|
context 'with interactor class TestInteractor' do
|
@@ -39,11 +40,31 @@ RSpec.describe ActiveInteractor::Interactor::Worker do
|
|
39
40
|
subject
|
40
41
|
end
|
41
42
|
|
43
|
+
context 'with options :skip_perform_callbacks eq to true' do
|
44
|
+
subject { described_class.new(interactor).execute_perform!(skip_perform_callbacks: true) }
|
45
|
+
|
46
|
+
it 'is expected not to run perform callbacks on interactor' do
|
47
|
+
allow_any_instance_of(TestInteractor).to receive(:run_callbacks)
|
48
|
+
.with(:validation).and_call_original
|
49
|
+
expect_any_instance_of(TestInteractor).not_to receive(:run_callbacks)
|
50
|
+
.with(:perform)
|
51
|
+
subject
|
52
|
+
end
|
53
|
+
|
54
|
+
it 'calls #perform on interactor instance' do
|
55
|
+
expect_any_instance_of(TestInteractor).to receive(:perform)
|
56
|
+
subject
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
42
60
|
context 'when interactor context is invalid on :calling' do
|
43
61
|
before do
|
44
62
|
allow_any_instance_of(TestInteractor.context_class).to receive(:valid?)
|
45
63
|
.with(:calling)
|
46
64
|
.and_return(false)
|
65
|
+
allow_any_instance_of(TestInteractor.context_class).to receive(:valid?)
|
66
|
+
.with(:called)
|
67
|
+
.and_return(true)
|
47
68
|
end
|
48
69
|
|
49
70
|
it { expect { subject }.to raise_error(ActiveInteractor::Error::ContextFailure) }
|
@@ -51,6 +72,33 @@ RSpec.describe ActiveInteractor::Interactor::Worker do
|
|
51
72
|
expect_any_instance_of(TestInteractor).to receive(:context_rollback!)
|
52
73
|
expect { subject }.to raise_error(ActiveInteractor::Error::ContextFailure)
|
53
74
|
end
|
75
|
+
|
76
|
+
context 'with options :validate eq to false' do
|
77
|
+
subject { described_class.new(interactor).execute_perform!(validate: false) }
|
78
|
+
|
79
|
+
it { expect { subject }.not_to raise_error }
|
80
|
+
it 'is expected not to run validation callbacks on interactor' do
|
81
|
+
expect_any_instance_of(TestInteractor).not_to receive(:run_callbacks)
|
82
|
+
.with(:validation)
|
83
|
+
subject
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
context 'with options :validate_on_calling eq to false' do
|
88
|
+
subject { described_class.new(interactor).execute_perform!(validate_on_calling: false) }
|
89
|
+
|
90
|
+
it { expect { subject }.not_to raise_error }
|
91
|
+
it 'is expected not to call valid? with :calling' do
|
92
|
+
expect_any_instance_of(TestInteractor.context_class).not_to receive(:valid?)
|
93
|
+
.with(:calling)
|
94
|
+
subject
|
95
|
+
end
|
96
|
+
it 'is expected to call valid? with :called' do
|
97
|
+
expect_any_instance_of(TestInteractor.context_class).to receive(:valid?)
|
98
|
+
.with(:called)
|
99
|
+
subject
|
100
|
+
end
|
101
|
+
end
|
54
102
|
end
|
55
103
|
|
56
104
|
context 'when interactor context is invalid on :called' do
|
@@ -68,6 +116,33 @@ RSpec.describe ActiveInteractor::Interactor::Worker do
|
|
68
116
|
expect_any_instance_of(TestInteractor).to receive(:context_rollback!)
|
69
117
|
expect { subject }.to raise_error(ActiveInteractor::Error::ContextFailure)
|
70
118
|
end
|
119
|
+
|
120
|
+
context 'with options :validate eq to false' do
|
121
|
+
subject { described_class.new(interactor).execute_perform!(validate: false) }
|
122
|
+
|
123
|
+
it { expect { subject }.not_to raise_error }
|
124
|
+
it 'is expected not to run validation callbacks on interactor' do
|
125
|
+
expect_any_instance_of(TestInteractor).not_to receive(:run_callbacks)
|
126
|
+
.with(:validation)
|
127
|
+
subject
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
context 'with options :validate_on_called eq to false' do
|
132
|
+
subject { described_class.new(interactor).execute_perform!(validate_on_called: false) }
|
133
|
+
|
134
|
+
it { expect { subject }.not_to raise_error }
|
135
|
+
it 'is expected to call valid? with :calling' do
|
136
|
+
expect_any_instance_of(TestInteractor.context_class).to receive(:valid?)
|
137
|
+
.with(:calling)
|
138
|
+
subject
|
139
|
+
end
|
140
|
+
it 'is expected not to call valid? with :called' do
|
141
|
+
expect_any_instance_of(TestInteractor.context_class).not_to receive(:valid?)
|
142
|
+
.with(:called)
|
143
|
+
subject
|
144
|
+
end
|
145
|
+
end
|
71
146
|
end
|
72
147
|
end
|
73
148
|
|
@@ -84,6 +159,30 @@ RSpec.describe ActiveInteractor::Interactor::Worker do
|
|
84
159
|
expect_any_instance_of(TestInteractor).to receive(:context_rollback!)
|
85
160
|
subject
|
86
161
|
end
|
162
|
+
|
163
|
+
context 'with options :skip_rollback eq to true' do
|
164
|
+
subject { described_class.new(interactor).execute_rollback(skip_rollback: true) }
|
165
|
+
|
166
|
+
it 'is expected not to call #context_rollback on interactor instance' do
|
167
|
+
expect_any_instance_of(TestInteractor).not_to receive(:context_rollback!)
|
168
|
+
subject
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
context 'with options :skip_rollback_callbacks eq to true' do
|
173
|
+
subject { described_class.new(interactor).execute_rollback(skip_rollback_callbacks: true) }
|
174
|
+
|
175
|
+
it 'is expected not to run rollback callbacks on interactor' do
|
176
|
+
expect_any_instance_of(TestInteractor).not_to receive(:run_callbacks)
|
177
|
+
.with(:rollback)
|
178
|
+
subject
|
179
|
+
end
|
180
|
+
|
181
|
+
it 'calls #context_rollback on interactor instance' do
|
182
|
+
expect_any_instance_of(TestInteractor).to receive(:context_rollback!)
|
183
|
+
subject
|
184
|
+
end
|
185
|
+
end
|
87
186
|
end
|
88
187
|
end
|
89
188
|
end
|
@@ -136,6 +136,7 @@ RSpec.describe ActiveInteractor::Organizer do
|
|
136
136
|
end
|
137
137
|
|
138
138
|
it { expect { subject }.not_to raise_error }
|
139
|
+
it { is_expected.to be_failure }
|
139
140
|
it { is_expected.to be_a interactor_class.context_class }
|
140
141
|
it 'is expected to call #perform on the first interactor' do
|
141
142
|
expect_any_instance_of(interactor1).to receive(:perform)
|
@@ -161,6 +162,7 @@ RSpec.describe ActiveInteractor::Organizer do
|
|
161
162
|
end
|
162
163
|
|
163
164
|
it { expect { subject }.not_to raise_error }
|
165
|
+
it { is_expected.to be_failure }
|
164
166
|
it { is_expected.to be_a interactor_class.context_class }
|
165
167
|
it 'is expected to call #perform on both interactors' do
|
166
168
|
expect_any_instance_of(interactor1).to receive(:perform)
|
@@ -173,6 +175,71 @@ RSpec.describe ActiveInteractor::Organizer do
|
|
173
175
|
subject
|
174
176
|
end
|
175
177
|
end
|
178
|
+
|
179
|
+
context 'when the organizer is set to perform in parallel' do
|
180
|
+
let(:interactor_class) do
|
181
|
+
build_organizer do
|
182
|
+
perform_in_parallel
|
183
|
+
|
184
|
+
organize TestInteractor1, TestInteractor2
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
it { is_expected.to be_a interactor_class.context_class }
|
189
|
+
it 'is expected to call #perform on both interactors' do
|
190
|
+
expect_any_instance_of(interactor1).to receive(:perform)
|
191
|
+
expect_any_instance_of(interactor2).to receive(:perform)
|
192
|
+
subject
|
193
|
+
end
|
194
|
+
|
195
|
+
context 'when the first interactor context fails' do
|
196
|
+
let!(:interactor1) do
|
197
|
+
build_interactor('TestInteractor1') do
|
198
|
+
def perform
|
199
|
+
context.fail!
|
200
|
+
end
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
it { expect { subject }.not_to raise_error }
|
205
|
+
it { is_expected.to be_failure }
|
206
|
+
it { is_expected.to be_a interactor_class.context_class }
|
207
|
+
it 'is expected to call #perform on both interactors' do
|
208
|
+
expect_any_instance_of(interactor1).to receive(:perform)
|
209
|
+
expect_any_instance_of(interactor2).to receive(:perform)
|
210
|
+
subject
|
211
|
+
end
|
212
|
+
it 'is expected to call #rollback both interactors' do
|
213
|
+
expect_any_instance_of(interactor1).to receive(:rollback)
|
214
|
+
expect_any_instance_of(interactor2).to receive(:rollback)
|
215
|
+
subject
|
216
|
+
end
|
217
|
+
end
|
218
|
+
|
219
|
+
context 'when the second interactor context fails' do
|
220
|
+
let!(:interactor2) do
|
221
|
+
build_interactor('TestInteractor2') do
|
222
|
+
def perform
|
223
|
+
context.fail!
|
224
|
+
end
|
225
|
+
end
|
226
|
+
end
|
227
|
+
|
228
|
+
it { expect { subject }.not_to raise_error }
|
229
|
+
it { is_expected.to be_failure }
|
230
|
+
it { is_expected.to be_a interactor_class.context_class }
|
231
|
+
it 'is expected to call #perform on both interactors' do
|
232
|
+
expect_any_instance_of(interactor1).to receive(:perform)
|
233
|
+
expect_any_instance_of(interactor2).to receive(:perform)
|
234
|
+
subject
|
235
|
+
end
|
236
|
+
it 'is expected to call #rollback on both interactors' do
|
237
|
+
expect_any_instance_of(interactor1).to receive(:rollback)
|
238
|
+
expect_any_instance_of(interactor2).to receive(:rollback)
|
239
|
+
subject
|
240
|
+
end
|
241
|
+
end
|
242
|
+
end
|
176
243
|
end
|
177
244
|
end
|
178
245
|
end
|
@@ -8,4 +8,16 @@ RSpec.describe ActiveInteractor::Rails::Config do
|
|
8
8
|
|
9
9
|
it { is_expected.to respond_to :directory }
|
10
10
|
it { is_expected.to respond_to :generate_context_classes }
|
11
|
+
|
12
|
+
describe '.defaults' do
|
13
|
+
subject { described_class.defaults }
|
14
|
+
|
15
|
+
it 'is expected to have attributes :directory => "interactors"' do
|
16
|
+
expect(subject[:directory]).to eq 'interactors'
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'is expected to have attributes :generate_context_classes => true' do
|
20
|
+
expect(subject[:generate_context_classes]).to eq true
|
21
|
+
end
|
22
|
+
end
|
11
23
|
end
|
@@ -217,4 +217,55 @@ RSpec.describe 'Basic Integration', type: :integration do
|
|
217
217
|
end
|
218
218
|
end
|
219
219
|
end
|
220
|
+
|
221
|
+
describe 'A basic organizer performing in parallel' do
|
222
|
+
let!(:test_interactor_1) do
|
223
|
+
build_interactor('TestInteractor1') do
|
224
|
+
def perform
|
225
|
+
context.test_field_1 = 'test 1'
|
226
|
+
end
|
227
|
+
end
|
228
|
+
end
|
229
|
+
|
230
|
+
let!(:test_interactor_2) do
|
231
|
+
build_interactor('TestInteractor2') do
|
232
|
+
def perform
|
233
|
+
context.test_field_2 = 'test 2'
|
234
|
+
end
|
235
|
+
end
|
236
|
+
end
|
237
|
+
|
238
|
+
let(:interactor_class) do
|
239
|
+
build_organizer do
|
240
|
+
perform_in_parallel
|
241
|
+
organize TestInteractor1, TestInteractor2
|
242
|
+
end
|
243
|
+
end
|
244
|
+
|
245
|
+
include_examples 'a class with interactor methods'
|
246
|
+
include_examples 'a class with interactor callback methods'
|
247
|
+
include_examples 'a class with interactor context methods'
|
248
|
+
include_examples 'a class with organizer callback methods'
|
249
|
+
|
250
|
+
describe '.context_class' do
|
251
|
+
subject { interactor_class.context_class }
|
252
|
+
|
253
|
+
it { is_expected.to eq TestOrganizer::Context }
|
254
|
+
it { is_expected.to be < ActiveInteractor::Context::Base }
|
255
|
+
end
|
256
|
+
|
257
|
+
describe '.organized' do
|
258
|
+
subject { interactor_class.organized }
|
259
|
+
|
260
|
+
it { is_expected.to eq [TestInteractor1, TestInteractor2] }
|
261
|
+
end
|
262
|
+
|
263
|
+
describe '.perform' do
|
264
|
+
subject { interactor_class.perform }
|
265
|
+
|
266
|
+
it { is_expected.to be_a interactor_class.context_class }
|
267
|
+
it { is_expected.to be_successful }
|
268
|
+
it { is_expected.to have_attributes(test_field_1: 'test 1', test_field_2: 'test 2') }
|
269
|
+
end
|
270
|
+
end
|
220
271
|
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.0.beta.
|
4
|
+
version: 1.0.0.beta.3
|
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-01-
|
11
|
+
date: 2020-01-12 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activemodel
|
@@ -107,6 +107,7 @@ files:
|
|
107
107
|
- lib/active_interactor.rb
|
108
108
|
- lib/active_interactor/base.rb
|
109
109
|
- lib/active_interactor/config.rb
|
110
|
+
- lib/active_interactor/configurable.rb
|
110
111
|
- lib/active_interactor/context.rb
|
111
112
|
- lib/active_interactor/context/attributes.rb
|
112
113
|
- lib/active_interactor/context/base.rb
|
@@ -115,6 +116,7 @@ files:
|
|
115
116
|
- lib/active_interactor/interactor.rb
|
116
117
|
- lib/active_interactor/interactor/callbacks.rb
|
117
118
|
- lib/active_interactor/interactor/context.rb
|
119
|
+
- lib/active_interactor/interactor/perform_options.rb
|
118
120
|
- lib/active_interactor/interactor/worker.rb
|
119
121
|
- lib/active_interactor/organizer.rb
|
120
122
|
- lib/active_interactor/rails.rb
|
@@ -166,10 +168,10 @@ licenses:
|
|
166
168
|
- MIT
|
167
169
|
metadata:
|
168
170
|
bug_tracker_uri: https://github.com/aaronmallen/activeinteractor/issues
|
169
|
-
changelog_uri: https://github.com/aaronmallen/activeinteractor/blob/v1.0.0.beta.
|
170
|
-
documentation_uri: https://www.rubydoc.info/gems/activeinteractor/1.0.0.beta.
|
171
|
+
changelog_uri: https://github.com/aaronmallen/activeinteractor/blob/v1.0.0.beta.3/CHANGELOG.md
|
172
|
+
documentation_uri: https://www.rubydoc.info/gems/activeinteractor/1.0.0.beta.3
|
171
173
|
hompage_uri: https://github.com/aaronmallen/activeinteractor
|
172
|
-
source_code_uri: https://github.com/aaronmallen/activeinteractor/tree/v1.0.0.beta.
|
174
|
+
source_code_uri: https://github.com/aaronmallen/activeinteractor/tree/v1.0.0.beta.3
|
173
175
|
wiki_uri: https://github.com/aaronmallen/activeinteractor/wiki
|
174
176
|
post_install_message:
|
175
177
|
rdoc_options: []
|