activeinteractor 1.0.0.beta.3 → 1.0.0.beta.4

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ec758d003a313ede93fb1abbabc42cae23e071b9c675a9515006a5fb0e3a02a4
4
- data.tar.gz: 6e1c83a0e0f7a37f63a9d9523cfbad34ccd8fb1996c5329e91294c772f4898bc
3
+ metadata.gz: b887e29c2334d776e28d0df4e79ba53b42123d47e4e6f4da6c94a2fa5b38ea78
4
+ data.tar.gz: e6ca29f29e435b5b73e405c41eccfa6dea8837a7316c3d3143abd97cda241347
5
5
  SHA512:
6
- metadata.gz: 91ccd10ef1bd421dcf3275a980fe279de279c60a52b4815c0f4ae8b44c6d4721ed135f2e3978dd1c6cca4bc1acc3fd064013023ac89e48a1fd4fb53ba4c32f7d
7
- data.tar.gz: fec0e7ab4a5976efae4b0c64cc4593789b914d8c6208966575af9f79b6ebbd495d30c2c9e5ef942a060334620b6dfca946c67c27a694a36295bf194d41cd4887
6
+ metadata.gz: 8f766aa0e2c50d7cec5c74def38f7e2ea9d3918354f511d2ec127cf4054c9484638f1cd6f984686e1b12ca09116f53708fd4e5f714620178c4dd73ab745d6ee3
7
+ data.tar.gz: 2ece11503295a16bfba3860cb8d4ad3653eca4a1399c5b8928b2780884ca81b48b3a6128c9fce11b464cd0bf2928d4f22924a7d2d6a4c4a00e71d9c7603375a2
@@ -7,6 +7,28 @@ and this project adheres to [Semantic Versioning].
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [v1.0.0-beta.4] - 2020-01-14
11
+
12
+ ### Added
13
+
14
+ - [#114] `ActiveInteractor::Organizer::InteractorInterface`
15
+ - [#114] `ActiveInteractor::Organizer::InteractorInterfaceCollection`
16
+ - [#115] `ActiveInteractor::Interactor#options`
17
+ - [#115] `ActiveInteractor::Interactor#with_options`
18
+ - [#115] `ActiveInteractor::Interactor::PerformOptions#skip_each_perform_callbacks`
19
+
20
+ ### Changed
21
+
22
+ - [#115] `ActiveInteractor::Interactor::Worker#execute_perform` and `#execute_perform!` no longer accept arguments,
23
+ use `ActiveInteractor::Interactor#with_options` instead.
24
+ - [#115] `ActiveInteractor::Organizer` can now skip `each_perform` callbacks with
25
+ the option `skip_each_perform_callbacks`
26
+
27
+ ### Removed
28
+
29
+ - [#115] `ActiveInteractor::Interactor#execute_rollback`
30
+ - [#115] `ActiveInteractor::Interactor::Worker#run_callbacks`
31
+
10
32
  ## [v1.0.0-beta.3] - 2020-01-12
11
33
 
12
34
  ### Added
@@ -147,7 +169,8 @@ and this project adheres to [Semantic Versioning].
147
169
 
148
170
  <!-- versions -->
149
171
 
150
- [Unreleased]: https://github.com/aaronmallen/activeinteractor/compare/v1.0.0-beta.3...HEAD
172
+ [Unreleased]: https://github.com/aaronmallen/activeinteractor/compare/v1.0.0-beta.4...HEAD
173
+ [v1.0.0-beta.4]: https://github.com/aaronmallen/activeinteractor/compare/v1.0.0-beta.3...v1.0.0-beta.4
151
174
  [v1.0.0-beta.3]: https://github.com/aaronmallen/activeinteractor/compare/v1.0.0-beta.2...v1.0.0-beta.3
152
175
  [v1.0.0-beta.2]: https://github.com/aaronmallen/activeinteractor/compare/v1.0.0-beta.1...v1.0.0-beta.2
153
176
  [v1.0.0-beta.1]: https://github.com/aaronmallen/activeinteractor/compare/v0.1.7...v1.0.0-beta.1
@@ -181,3 +204,5 @@ and this project adheres to [Semantic Versioning].
181
204
  [#105]: https://github.com/aaronmallen/activeinteractor/pull/105
182
205
  [#109]: https://github.com/aaronmallen/activeinteractor/pull/109
183
206
  [#110]: https://github.com/aaronmallen/activeinteractor/pull/110
207
+ [#114]:https://github.com/aaronmallen/activeinteractor/pull/114
208
+ [#115]: https://github.com/aaronmallen/activeinteractor/pull/115
data/README.md CHANGED
@@ -11,6 +11,9 @@
11
11
 
12
12
  Ruby interactors with [ActiveModel::Validations] based on the [interactor][collective_idea_interactors] gem.
13
13
 
14
+ **ActiveInteractor v1.0.0 is currently in beta. For documentation on the current stable version please
15
+ see [v0.1.7](https://github.com/aaronmallen/activeinteractor/tree/0-1-stable)**
16
+
14
17
  <!-- TOC -->
15
18
 
16
19
  * [Getting Started](#getting-started)
@@ -26,7 +29,8 @@ Ruby interactors with [ActiveModel::Validations] based on the [interactor][colle
26
29
  * [Kinds of Interactors](#kinds-of-interactors)
27
30
  * [Interactors](#interactors)
28
31
  * [Organizers](#organizers)
29
- * [Parallel Organizers](#parallel-organizers)
32
+ * [Organizing Interactors Conditionally](#organizing-interactors-conditionally)
33
+ * [Running Interactors In Parallel](#running-interactors-in-parallel)
30
34
  * [Rollback](#rollback)
31
35
  * [Callbacks](#callbacks)
32
36
  * [Validation Callbacks](#validation-callbacks)
@@ -46,7 +50,7 @@ Ruby interactors with [ActiveModel::Validations] based on the [interactor][colle
46
50
  Add this line to your application's Gemfile:
47
51
 
48
52
  ```ruby
49
- gem 'activeinteractor'
53
+ gem 'activeinteractor', '~> 1.0.0.beta.3'
50
54
  ```
51
55
 
52
56
  And then execute:
@@ -58,7 +62,7 @@ bundle
58
62
  Or install it yourself as:
59
63
 
60
64
  ```bash
61
- gem install activeinteractor
65
+ gem install activeinteractor --pre
62
66
  ```
63
67
 
64
68
  ## What is an Interactor
@@ -422,7 +426,27 @@ end
422
426
  The organizer passes its context to the interactors that it organizes, one at a time and in order. Each interactor may
423
427
  change that context before it's passed along to the next interactor.
424
428
 
425
- ##### Parallel Organizers
429
+ ###### Organizing Interactors Conditionally
430
+
431
+ We can also add conditional statements to our organizer by passing a block to the `#organize` method:
432
+
433
+ ```ruby
434
+ class PlaceOrder < ActiveInteractor::Organizer
435
+ organize do
436
+ add :create_order, if :user_registered?
437
+ add :charge_card, if: -> { context.order_number }
438
+ add :send_thank_you, if: -> { context.order_number }
439
+ end
440
+
441
+ private
442
+
443
+ def user_registered?
444
+ context.user&.registered?
445
+ end
446
+ end
447
+ ```
448
+
449
+ ###### Running Interactors In Parallel
426
450
 
427
451
  Organizers can be told to run their interactors in parallel with the `#perform_in_parallel` class method. This
428
452
  will run each interactor in parallel with one and other only passing the original context to each organizer.
@@ -11,7 +11,7 @@ module ActiveInteractor
11
11
  extend ClassMethods
12
12
  include Callbacks
13
13
  include Context
14
- delegate :execute_perform, :execute_perform!, :execute_rollback, to: :worker
14
+ delegate :execute_perform, :execute_perform!, to: :worker
15
15
  end
16
16
  end
17
17
 
@@ -23,10 +23,11 @@ module ActiveInteractor
23
23
  # MyInteractor.perform(name: 'Aaron')
24
24
  # #=> <#MyInteractor::Context name='Aaron'>
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
+ # @param options [Hash] execution options for the interactor perform step
27
+ # see {PerformOptions}
27
28
  # @return [Context::Base] an instance of context.
28
- def perform(context = {}, options = PerformOptions.new)
29
- new(context).execute_perform(options)
29
+ def perform(context = {}, options = {})
30
+ new(context).with_options(options).execute_perform
30
31
  end
31
32
 
32
33
  # Run an interactor context. The {.perform!} method behaves identically to
@@ -37,14 +38,22 @@ module ActiveInteractor
37
38
  # MyInteractor.perform!(name: 'Aaron')
38
39
  # #=> <#MyInteractor::Context name='Aaron'>
39
40
  # @param context [Hash|Context::Base] attributes to assign to the interactor context
40
- # @param options [PerformOptions|Hash] execution options for the interactor perform step
41
+ # @param options [Hash] execution options for the interactor perform step
42
+ # see {PerformOptions}
41
43
  # @raise [Error::ContextFailure] if the context fails.
42
44
  # @return [Context::Base] an instance of context.
43
- def perform!(context = {}, options = PerformOptions.new)
44
- new(context).execute_perform!(options)
45
+ def perform!(context = {}, options = {})
46
+ new(context).with_options(options).execute_perform!
45
47
  end
46
48
  end
47
49
 
50
+ # Options for invokation of {#perform}
51
+ # @since 1.0.0
52
+ # @return [PerformOptions] the options
53
+ def options
54
+ @options ||= PerformOptions.new
55
+ end
56
+
48
57
  # Invoke an Interactor instance without any hooks, tracking, or rollback
49
58
  # @abstract It is expected that the {#perform} method is overwritten
50
59
  # for each interactor class.
@@ -55,6 +64,15 @@ module ActiveInteractor
55
64
  # failure is expected to overwrite the {#rollback} method.
56
65
  def rollback; end
57
66
 
67
+ # Set options for invokation of {#perform}
68
+ # @since 1.0.0
69
+ # @param options [PerformOptions|Hash] the perform options
70
+ # @return [Base] the instance of {Base}
71
+ def with_options(options)
72
+ @options = options.is_a?(PerformOptions) ? options : PerformOptions.new(options)
73
+ self
74
+ end
75
+
58
76
  private
59
77
 
60
78
  def worker
@@ -5,6 +5,8 @@ module ActiveInteractor
5
5
  # Options object for interactor perform
6
6
  # @author Aaron Allen <hello@aaronmallen.me>
7
7
  # @since 1.0.0
8
+ # @!attribute [rw] skip_each_perform_callbacks
9
+ # @return [Boolean] whether or not to skip :each_perform callbacks for an {Organizer}
8
10
  # @!attribute [rw] skip_perform_callbacks
9
11
  # @return [Boolean] whether or not to skip :perform callbacks
10
12
  # @!attribute [rw] skip_rollback
@@ -19,27 +21,9 @@ module ActiveInteractor
19
21
  # @!attribute [rw] validate_on_called
20
22
  # @return [Boolean] whether or not to run validation on :called
21
23
  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
24
+ include ActiveInteractor::Configurable
25
+ defaults skip_each_perform_callbacks: false, skip_perform_callbacks: false, skip_rollback: false,
26
+ skip_rollback_callbacks: false, validate: true, validate_on_calling: true, validate_on_called: true
43
27
  end
44
28
  end
45
29
  end
@@ -8,108 +8,99 @@ module ActiveInteractor
8
8
  # @author Aaron Allen <hello@aaronmallen.me>
9
9
  # @since 0.0.2
10
10
  class Worker
11
- delegate :run_callbacks, to: :interactor
12
-
13
11
  # @param interactor [Base] an instance of interactor
14
12
  # @return [Worker] a new instance of {Worker}
15
13
  def initialize(interactor)
16
- @interactor = interactor.dup
14
+ options = interactor.options.dup
15
+ @interactor = interactor.dup.with_options(options)
17
16
  end
18
17
 
19
18
  # Calls {#execute_perform!} and rescues {Error::ContextFailure}
20
- # @param options [PerformOptions|Hash] execution options for the interactor perform step
21
19
  # @return [Context::Base] an instance of {Context::Base}
22
- def execute_perform(options = PerformOptions.new)
23
- execute_perform!(options)
20
+ def execute_perform
21
+ execute_perform!
24
22
  rescue Error::ContextFailure => e
25
23
  ActiveInteractor.logger.error("ActiveInteractor: #{e}")
26
24
  context
27
25
  end
28
26
 
29
27
  # Calls {Interactor#perform} with callbacks and context validation
30
- # @param options [PerformOptions|Hash] execution options for the interactor perform step
31
28
  # @raise [Error::ContextFailure] if the context fails
32
29
  # @return [Context::Base] an instance of {Context::Base}
33
- def execute_perform!(options = PerformOptions.new)
34
- options = parse_options(options)
35
- execute_context!(options)
30
+ def execute_perform!
31
+ execute_context!
36
32
  rescue StandardError => e
37
- handle_error(e, options)
33
+ handle_error(e)
38
34
  end
39
35
 
40
36
  # Calls {Interactor#rollback} with callbacks
41
37
  # @return [Boolean] `true` if rolled back successfully or `false` if already
42
38
  # rolled back
43
- def execute_rollback(options = PerformOptions.new)
44
- options = parse_options(options)
45
- return if options.skip_rollback
39
+ def execute_rollback
40
+ return if interactor.options.skip_rollback
46
41
 
47
- execute_interactor_rollback!(options)
42
+ execute_interactor_rollback!
48
43
  end
49
44
 
50
45
  private
51
46
 
52
47
  attr_reader :context, :interactor
53
48
 
54
- def execute_context!(options)
55
- if options.skip_perform_callbacks
56
- execute_context_with_validation_check!(options)
49
+ def execute_context!
50
+ if interactor.options.skip_perform_callbacks
51
+ execute_context_with_validation_check!
57
52
  else
58
- execute_context_with_callbacks!(options)
53
+ execute_context_with_callbacks!
59
54
  end
60
55
  end
61
56
 
62
- def execute_context_with_callbacks!(options)
63
- run_callbacks :perform do
64
- execute_context_with_validation_check!(options)
57
+ def execute_context_with_callbacks!
58
+ interactor.run_callbacks :perform do
59
+ execute_context_with_validation_check!
65
60
  @context = interactor.finalize_context!
66
61
  end
67
62
  end
68
63
 
69
- def execute_context_with_validation!(options)
70
- validate_on_calling(options)
64
+ def execute_context_with_validation!
65
+ validate_on_calling
71
66
  interactor.perform
72
- validate_on_called(options)
67
+ validate_on_called
73
68
  end
74
69
 
75
- def execute_context_with_validation_check!(options)
76
- return interactor.perform unless options.validate
70
+ def execute_context_with_validation_check!
71
+ return interactor.perform unless interactor.options.validate
77
72
 
78
- execute_context_with_validation!(options)
73
+ execute_context_with_validation!
79
74
  end
80
75
 
81
- def execute_interactor_rollback!(options)
82
- return interactor.context_rollback! if options.skip_rollback_callbacks
76
+ def execute_interactor_rollback!
77
+ return interactor.context_rollback! if interactor.options.skip_rollback_callbacks
83
78
 
84
- run_callbacks :rollback do
79
+ interactor.run_callbacks :rollback do
85
80
  interactor.context_rollback!
86
81
  end
87
82
  end
88
83
 
89
- def handle_error(exception, options)
84
+ def handle_error(exception)
90
85
  @context = interactor.finalize_context!
91
- execute_rollback(options)
86
+ execute_rollback
92
87
  raise exception
93
88
  end
94
89
 
95
- def parse_options(options)
96
- @options = options.is_a?(PerformOptions) ? options : PerformOptions.new(options)
97
- end
98
-
99
90
  def validate_context(validation_context = nil)
100
- run_callbacks :validation do
91
+ interactor.run_callbacks :validation do
101
92
  interactor.context_valid?(validation_context)
102
93
  end
103
94
  end
104
95
 
105
- def validate_on_calling(options)
106
- return unless options.validate_on_calling
96
+ def validate_on_calling
97
+ return unless interactor.options.validate_on_calling
107
98
 
108
99
  interactor.context_fail! unless validate_context(:calling)
109
100
  end
110
101
 
111
- def validate_on_called(options)
112
- return unless options.validate_on_called
102
+ def validate_on_called
103
+ return unless interactor.options.validate_on_called
113
104
 
114
105
  interactor.context_fail! unless validate_context(:called)
115
106
  end
@@ -1,16 +1,14 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'active_support/core_ext/class/attribute'
4
- require 'active_support/core_ext/string/inflections'
4
+
5
+ require 'active_interactor/organizer/interactor_interface_collection'
5
6
 
6
7
  module ActiveInteractor
7
8
  # A base Organizer class. All organizers should inherit from
8
9
  # {Organizer}.
9
10
  # @author Aaron Allen <hello@aaronmallen.me>
10
11
  # @since 0.0.1
11
- # @!attribute [r] organized
12
- # @!scope class
13
- # @return [Array<Base>] the organized interactors
14
12
  # @!attribute [r] parallel
15
13
  # @since 1.0.0
16
14
  # @!scope class
@@ -36,7 +34,6 @@ module ActiveInteractor
36
34
  # MyOrganizer.perform
37
35
  # #=> <MyOrganizer::Context interactor1=true interactor2=true>
38
36
  class Organizer < Base
39
- class_attribute :organized, instance_writer: false, default: []
40
37
  class_attribute :parallel, instance_writer: false, default: false
41
38
  define_callbacks :each_perform
42
39
 
@@ -83,7 +80,6 @@ module ActiveInteractor
83
80
 
84
81
  # Define a callback to call around each organized interactor's
85
82
  # {Base.perform} has been invokation
86
- #
87
83
  # @example
88
84
  # class MyInteractor1 < ActiveInteractor::Base
89
85
  # before_perform :print_name
@@ -129,7 +125,6 @@ module ActiveInteractor
129
125
 
130
126
  # Define a callback to call before each organized interactor's
131
127
  # {Base.perform} has been invoked
132
- #
133
128
  # @example
134
129
  # class MyInteractor1 < ActiveInteractor::Base
135
130
  # before_perform :print_name
@@ -172,21 +167,50 @@ module ActiveInteractor
172
167
  # Declare Interactors to be invoked as part of the
173
168
  # organizer's invocation. These interactors are invoked in
174
169
  # the order in which they are declared
175
- #
176
- # @example
177
- # class MyFirstOrganizer < ActiveInteractor::Organizer
178
- # organize InteractorOne, InteractorTwo
170
+ # @example Basic interactor organization
171
+ # class MyOrganizer < ActiveInteractor::Organizer
172
+ # organize :interactor_one, :interactor_two
179
173
  # end
180
- #
181
- # class MySecondOrganizer < ActiveInteractor::Organizer
182
- # organize [InteractorThree, InteractorFour]
174
+ # @example Conditional interactor organization with block
175
+ # class MyOrganizer < ActiveInteractor::Organizer
176
+ # organize do
177
+ # add :interactor_one
178
+ # add :interactor_two, if: -> { context.valid? }
179
+ # end
183
180
  # end
181
+ # @example Conditional interactor organization with method
182
+ # class MyOrganizer < ActiveInteractor::Organizer
183
+ # organize do
184
+ # add :interactor_one
185
+ # add :interactor_two, unless: :invalid_context?
186
+ # end
187
+ #
188
+ # private
184
189
  #
185
- # @param interactors [Array<Base>] the interactors to call
186
- def self.organize(*interactors)
187
- self.organized = interactors.flatten.map do |interactor|
188
- interactor.to_s.classify.safe_constantize
189
- end.compact
190
+ # def invalid_context?
191
+ # !context.valid?
192
+ # end
193
+ # end
194
+ # @example Interactor organization with perform options
195
+ # class MyOrganizer < ActiveInteractor::Organizer
196
+ # organize do
197
+ # add :interactor_one, validate: false
198
+ # add :interactor_two, skip_perform_callbacks: true
199
+ # end
200
+ # end
201
+ # @param interactors [Array<Base|Symbol|String>] the interactors to call
202
+ # @yield [.organized] if block given
203
+ # @return [InteractorInterfaceCollection] an instance of {InteractorInterfaceCollection}
204
+ def self.organize(*interactors, &block)
205
+ organized.concat(interactors) if interactors
206
+ organized.instance_eval(&block) if block
207
+ organized
208
+ end
209
+
210
+ # Organized interactors
211
+ # @return [InteractorInterfaceCollection] an instance of {InteractorInterfaceCollection}
212
+ def self.organized
213
+ @organized ||= InteractorInterfaceCollection.new
190
214
  end
191
215
 
192
216
  # Run organized interactors in parallel
@@ -196,7 +220,7 @@ module ActiveInteractor
196
220
  end
197
221
 
198
222
  # Invoke the organized interactors. An organizer is
199
- # expected not to define its own {Base#perform} method
223
+ # expected not to define its own {Interactor#perform #perform} method
200
224
  # in favor of this default implementation.
201
225
  def perform
202
226
  if self.class.parallel
@@ -208,11 +232,16 @@ module ActiveInteractor
208
232
 
209
233
  private
210
234
 
211
- def execute_interactor_with_callbacks(interactor, fail_on_error = false, execute_options = {})
235
+ def execute_interactor(interface, fail_on_error = false, perform_options = {})
236
+ interface.perform(self, context, fail_on_error, perform_options)
237
+ end
238
+
239
+ def execute_interactor_with_callbacks(interface, fail_on_error = false, perform_options = {})
240
+ args = [interface, fail_on_error, perform_options]
241
+ return execute_interactor(*args) if options.skip_each_perform_callbacks
242
+
212
243
  run_callbacks :each_perform do
213
- instance = interactor.new(context)
214
- method = fail_on_error ? :execute_perform! : :execute_perform
215
- instance.send(method, execute_options)
244
+ execute_interactor(*args)
216
245
  end
217
246
  end
218
247
 
@@ -222,16 +251,17 @@ module ActiveInteractor
222
251
  end
223
252
 
224
253
  def perform_in_order
225
- self.class.organized.each do |interactor|
226
- context.merge!(execute_interactor_with_callbacks(interactor, true))
254
+ self.class.organized.each do |interface|
255
+ result = execute_interactor_with_callbacks(interface, true)
256
+ context.merge!(result) if result
227
257
  end
228
258
  rescue Error::ContextFailure => e
229
259
  context.merge!(e.context)
230
260
  end
231
261
 
232
262
  def perform_in_parallel
233
- results = self.class.organized.map do |interactor|
234
- Thread.new { execute_interactor_with_callbacks(interactor, false, skip_rollback: true) }
263
+ results = self.class.organized.map do |interface|
264
+ Thread.new { execute_interactor_with_callbacks(interface, false, skip_rollback: true) }
235
265
  end
236
266
  merge_contexts(results.map(&:value))
237
267
  end