activeinteractor 1.0.0.beta.2 → 1.0.0.beta.3
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 +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: []
|