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

Sign up to get free protection for your applications and to get access to all the features.
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