activeinteractor 0.1.7 → 1.0.0.beta.1

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.
Files changed (58) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +36 -1
  3. data/README.md +397 -395
  4. data/lib/active_interactor.rb +12 -30
  5. data/lib/active_interactor/base.rb +18 -8
  6. data/lib/active_interactor/context.rb +4 -181
  7. data/lib/active_interactor/context/attributes.rb +37 -149
  8. data/lib/active_interactor/context/base.rb +141 -0
  9. data/lib/active_interactor/context/loader.rb +45 -0
  10. data/lib/active_interactor/error.rb +22 -15
  11. data/lib/active_interactor/interactor.rb +24 -57
  12. data/lib/active_interactor/interactor/callbacks.rb +64 -76
  13. data/lib/active_interactor/interactor/context.rb +97 -63
  14. data/lib/active_interactor/interactor/worker.rb +22 -65
  15. data/lib/active_interactor/organizer.rb +180 -164
  16. data/lib/active_interactor/version.rb +2 -3
  17. data/lib/rails/generators/active_interactor.rb +2 -37
  18. data/lib/rails/generators/active_interactor/application_interactor_generator.rb +23 -0
  19. data/lib/rails/generators/active_interactor/install_generator.rb +8 -12
  20. data/lib/rails/generators/active_interactor/templates/application_context.rb +4 -0
  21. data/lib/rails/generators/{templates/application_interactor.erb → active_interactor/templates/application_interactor.rb} +0 -0
  22. data/lib/rails/generators/active_interactor/templates/application_organizer.rb +4 -0
  23. data/lib/rails/generators/active_interactor/templates/initializer.erb +5 -0
  24. data/lib/rails/generators/interactor/context/rspec_generator.rb +19 -0
  25. data/lib/rails/generators/interactor/context/templates/rspec.erb +7 -0
  26. data/lib/rails/generators/interactor/context/templates/test_unit.erb +9 -0
  27. data/lib/rails/generators/interactor/context/test_unit_generator.rb +19 -0
  28. data/lib/rails/generators/interactor/context_generator.rb +19 -0
  29. data/lib/rails/generators/interactor/interactor_generator.rb +8 -3
  30. data/lib/rails/generators/interactor/organizer_generator.rb +8 -3
  31. data/lib/rails/generators/interactor/rspec_generator.rb +4 -3
  32. data/lib/rails/generators/interactor/templates/context.erb +4 -0
  33. data/lib/rails/generators/{templates → interactor/templates}/interactor.erb +0 -0
  34. data/lib/rails/generators/{templates → interactor/templates}/organizer.erb +1 -1
  35. data/lib/rails/generators/{templates → interactor/templates}/rspec.erb +0 -0
  36. data/lib/rails/generators/{templates → interactor/templates}/test_unit.erb +0 -0
  37. data/lib/rails/generators/interactor/test_unit_generator.rb +4 -3
  38. data/spec/active_interactor/base_spec.rb +51 -0
  39. data/spec/active_interactor/context/base_spec.rb +229 -0
  40. data/spec/active_interactor/error_spec.rb +43 -0
  41. data/spec/active_interactor/interactor/worker_spec.rb +89 -0
  42. data/spec/active_interactor/organizer_spec.rb +178 -0
  43. data/spec/active_interactor_spec.rb +26 -0
  44. data/spec/integration/basic_callback_integration_spec.rb +355 -0
  45. data/spec/integration/basic_context_integration_spec.rb +73 -0
  46. data/spec/integration/basic_integration_spec.rb +220 -0
  47. data/spec/integration/basic_validations_integration_spec.rb +204 -0
  48. data/spec/spec_helper.rb +44 -0
  49. data/spec/support/helpers/factories.rb +41 -0
  50. data/spec/support/shared_examples/a_class_with_interactor_callback_methods_example.rb +99 -0
  51. data/spec/support/shared_examples/a_class_with_interactor_context_methods_example.rb +58 -0
  52. data/spec/support/shared_examples/a_class_with_interactor_methods_example.rb +21 -0
  53. data/spec/support/shared_examples/a_class_with_organizer_callback_methods_example.rb +39 -0
  54. data/spec/support/spec_helpers.rb +7 -0
  55. metadata +68 -138
  56. data/lib/active_interactor/configuration.rb +0 -38
  57. data/lib/active_interactor/interactor/execution.rb +0 -24
  58. data/lib/rails/generators/templates/initializer.erb +0 -15
@@ -1,96 +1,130 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'active_model'
4
+ require 'active_support/core_ext/string/inflections'
5
+
3
6
  module ActiveInteractor
4
7
  module Interactor
5
- # Provides ActiveInteractor::Interactor::Context methods to included classes
6
- #
7
- # @api private
8
+ # Interactor context methods included by all {Base}
8
9
  # @author Aaron Allen <hello@aaronmallen.me>
9
10
  # @since 0.0.1
10
- # @version 0.2
11
+ # @!attribute [rw] context
12
+ # @api private
13
+ # @return [Context::Base] an instance of the interactor's context class
11
14
  module Context
12
- extend ActiveSupport::Concern
15
+ # @!method context_fail!(errors = nil)
16
+ # Fail the interactor's context
17
+ # @since 1.0.0
18
+ # @see ActiveInteractor::Context::Base#fail!
19
+
20
+ # @!method context_rollback!
21
+ # Rollback the interactor's context
22
+ # @since 1.0.0
23
+ # @see ActiveInteractor::Context::Base#rollback!
24
+
25
+ # @!method context_valid?(context = nil)
26
+ # Whether or not the interactor's context is valid
27
+ # @since 1.0.0
28
+ # @see ActiveInteractor::Context::Base#valid?
29
+ def self.included(base)
30
+ base.class_eval do
31
+ extend ClassMethods
32
+ delegate :fail!, :rollback!, to: :context, prefix: true
33
+ delegate(*ActiveModel::Validations.instance_methods, to: :context, prefix: true)
34
+ delegate(*ActiveModel::Validations::HelperMethods.instance_methods, to: :context, prefix: true)
13
35
 
14
- included do
15
- extend ClassMethods
16
- include Callbacks
36
+ private
17
37
 
18
- delegate(*ActiveModel::Validations.instance_methods, to: :context, prefix: true)
19
- delegate(*ActiveModel::Validations::HelperMethods.instance_methods, to: :context, prefix: true)
38
+ attr_accessor :context
39
+ end
20
40
  end
21
41
 
42
+ # Interactor context class methods extended by all {Base}
22
43
  module ClassMethods
23
44
  delegate(*ActiveModel::Validations::ClassMethods.instance_methods, to: :context_class, prefix: :context)
24
45
  delegate(*ActiveModel::Validations::HelperMethods.instance_methods, to: :context_class, prefix: :context)
46
+ # @!method context_attributes(*attributes)
47
+ # Set or get attributes defined on the interactor's context class
48
+ # @since 0.0.1
49
+ # @see ActiveInteractor::Context::Attributes#attributes
50
+ delegate :attributes, to: :context_class, prefix: :context
25
51
 
26
- # Create the context class for inherited classes.
27
- def inherited(base)
28
- base.const_set 'Context', Class.new(ActiveInteractor::Context::Base)
29
- end
30
-
31
- # Assign attributes to the context class of the interactor
32
- #
33
- # @example Assign attributes to the context class
34
- # class MyInteractor > ActiveInteractor::Base
35
- # context_attributes :first_name, :last_name
52
+ # The context class of the interactor
53
+ # @note If a context class is not defined on the interactor
54
+ # with {#contextualize_with} ActiveInteractor will attempt
55
+ # to find a class with the naming conventions:
56
+ # `MyInteractor::Context` or `MyInteractorContext`
57
+ # if no matching context class is found a context class
58
+ # will be created with the naming convention: `MyInteractor::Context`.
59
+ # @example With an existing class named `MyInteractor::Context`
60
+ # class MyInteractor::Context < ActiveInteractor::Context::Base
36
61
  # end
37
62
  #
38
- # MyInteractor::Context.attributes
39
- # #=> [:first_name, :last_name]
40
- # @param attributes [Array] the attributes to assign to the context
41
- def context_attributes(*attributes)
42
- context_class.attributes = attributes
43
- end
44
-
45
- # Assign attribute aliases to the context class of the interactor
46
- # any aliases passed to the context will be assigned to the
47
- # key of the aliases hash
48
- #
49
- # @example Assign attribute aliases to the context class
50
- # class MyInteractor > ActiveInteractor::Base
51
- # context_attributes :first_name, :last_name
52
- # context_attribute_aliases last_name: :sir_name
63
+ # class MyInteractor < ActiveInteractor::Base
53
64
  # end
54
65
  #
55
- # MyInteractor::Context.attribute_aliases
56
- # #=> { last_name: [:sir_name] }
57
- #
58
- # class MyInteractor > ActiveInteractor::Base
59
- # context_attributes :first_name, :last_name
60
- # context_attribute_aliases last_name: %i[sir_name, sirname]
66
+ # MyInteractor.context_class
67
+ # #=> MyInteractor::Context
68
+ # @example With an existing class named `MyInteractorContext`
69
+ # class MyInteractorContext < ActiveInteractor::Context::Base
61
70
  # end
62
71
  #
63
- # MyInteractor::Context.attribute_aliases
64
- # #=> { last_name: [:sir_name, :sirname] }
65
- #
66
- # context = MyInteractor.perform(first_name: 'Aaron', sir_name: 'Allen')
67
- # #=> <#MyInteractor::Context first_name='Aaron', last_name='Allen')
68
- #
69
- # context.sir_name
70
- # #=> nil
71
- #
72
- # context.last_name
73
- # #=> 'Allen'
74
- #
75
- # @param aliases [Hash{Symbol => Symbol, Array<Symbol>}] the attribute aliases
76
- #
77
- # @return [Hash{Symbol => Array<Symbol>}] the attribute aliases
78
- def context_attribute_aliases(aliases = {})
79
- context_class.alias_attributes(aliases)
80
- end
81
-
82
- # The context class of the interactor
72
+ # class MyInteractor < ActiveInteractor::Base
73
+ # end
83
74
  #
84
- # @example
75
+ # MyInteractor.context_class
76
+ # #=> MyInteractorContext
77
+ # @example With no existing context class
85
78
  # class MyInteractor < ActiveInteractor::Base
86
79
  # end
87
80
  #
88
81
  # MyInteractor.context_class
89
82
  # #=> MyInteractor::Context
83
+ # @return [Class] the interactor's context class
90
84
  def context_class
91
- const_get 'Context'
85
+ @context_class ||= ActiveInteractor::Context::Loader.find_or_create(self)
86
+ end
87
+
88
+ # Manual define an interactor's context class
89
+ # @since 1.0.0
90
+ # @example
91
+ # class AGenericContext < ActiveInteractor::Context::Base
92
+ # end
93
+ #
94
+ # class MyInteractor < ActiveInteractor::Base
95
+ # contextualize_with :a_generic_context
96
+ # end
97
+ #
98
+ # MyInteractor.context_class
99
+ # #=> AGenericContext
100
+ # @param klass [String|Symbol|Class] the context class
101
+ # @raise [Error::InvalidContextClass] if no matching class can be found
102
+ # @return [Class] the context class
103
+ def contextualize_with(klass)
104
+ @context_class = begin
105
+ context_class = klass.to_s.classify.safe_constantize
106
+ raise(Error::InvalidContextClass, klass) unless context_class
107
+
108
+ context_class
109
+ end
92
110
  end
93
111
  end
112
+
113
+ # @api private
114
+ # @param context [Context::Base|Hash] attributes to assign to the context
115
+ # @return [Base] a new instane of {Base}
116
+ def initialize(context = {})
117
+ @context = self.class.context_class.new(context)
118
+ end
119
+
120
+ # @api private
121
+ # Mark the interactor's context as called and return the context
122
+ # @since 1.0.0
123
+ # @return [Context::Base] an instance of the interactor's context class
124
+ def finalize_context!
125
+ context.called!(self)
126
+ context
127
+ end
94
128
  end
95
129
  end
96
130
  end
@@ -2,39 +2,40 @@
2
2
 
3
3
  module ActiveInteractor
4
4
  module Interactor
5
- # A worker class responsible for thread safe execution
6
- # of interactor {.perform} methods.
7
- #
5
+ # @api private
6
+ # Thread safe execution of interactor {Interactor#perform #perform} and
7
+ # {Interactor#rollback #rollback} methods.
8
8
  # @author Aaron Allen <hello@aaronmallen.me>
9
9
  # @since 0.0.2
10
- # @version 0.1
11
10
  class Worker
12
11
  delegate :run_callbacks, to: :interactor
13
12
 
14
- # A new instance of {Worker}
15
- # @param interactor [ActiveInteractor::Base] an interactor instance
16
- # @return [ActiveInteractor::Interactor::Worker] a new instance of {Worker}
13
+ # @param interactor [Base] an instance of interactor
14
+ # @return [Worker] a new instance of {Worker}
17
15
  def initialize(interactor)
18
- @interactor = clone_interactor(interactor)
16
+ @interactor = interactor.dup
19
17
  end
20
18
 
21
- # Calls {#execute_perform!} and rescues {ActiveInteractor::Error::ContextFailure}
22
- # @return [ActiveInteractor::Context::Base] an instance of {ActiveInteractor::Context::Base}
19
+ # Calls {#execute_perform!} and rescues {Error::ContextFailure}
20
+ # @return [Context::Base] an instance of {Context::Base}
23
21
  def execute_perform
24
22
  execute_perform!
25
- rescue ActiveInteractor::Error::ContextFailure => e
26
- log_context_failure(e)
23
+ rescue Error::ContextFailure => e
24
+ ActiveInteractor.logger.error("ActiveInteractor: #{e}")
27
25
  context
28
26
  end
29
27
 
30
28
  # Calls {Interactor#perform} with callbacks and context validation
31
- # @raise [ActiveInteractor::Error::ContextFailure] if the context fails
32
- # @return [ActiveInteractor::Context::Base] an instance of {ActiveInteractor::Context::Base}
29
+ # @raise [Error::ContextFailure] if the context fails
30
+ # @return [Context::Base] an instance of {Context::Base}
33
31
  def execute_perform!
34
32
  run_callbacks :perform do
35
33
  execute_context!
36
- rescue => e # rubocop:disable Style/RescueStandardError
37
- rollback_context_and_raise!(e)
34
+ @context = interactor.finalize_context!
35
+ rescue StandardError
36
+ @context = interactor.finalize_context!
37
+ execute_rollback
38
+ raise
38
39
  end
39
40
  end
40
41
 
@@ -43,67 +44,23 @@ module ActiveInteractor
43
44
  # rolled back
44
45
  def execute_rollback
45
46
  run_callbacks :rollback do
46
- interactor.rollback
47
+ interactor.context_rollback!
47
48
  end
48
49
  end
49
50
 
50
51
  private
51
52
 
52
- attr_reader :interactor
53
-
54
- def clone_interactor(interactor)
55
- cloned = interactor.clone
56
- cloned.send(:context=, cloned.send(:context).clone)
57
- cloned
58
- end
59
-
60
- def context
61
- interactor.send(:context)
62
- end
53
+ attr_reader :context, :interactor
63
54
 
64
55
  def execute_context!
65
- perform!
66
- context
67
- ensure
68
- finalize_context!
69
- end
70
-
71
- def fail_on_invalid_context!(validation_context = nil)
72
- context.fail! if should_fail_on_invalid_context?(validation_context)
73
- end
74
-
75
- def finalize_context!
76
- context.clean! if interactor.should_clean_context?
77
- context.called!
78
- end
79
-
80
- def log_context_failure(exception)
81
- ActiveInteractor.logger.error("ActiveInteractor: #{exception}")
82
- end
83
-
84
- def perform!
85
- fail_on_invalid_context!(:calling)
56
+ interactor.context_fail! unless validate_context(:calling)
86
57
  interactor.perform
87
- fail_on_invalid_context!(:called)
88
- end
89
-
90
- def rollback_context_and_raise!(exception)
91
- context.rollback!
92
- context.send(:mark_failed!)
93
- raise exception
94
- end
95
-
96
- def should_fail_on_invalid_context?(validation_context)
97
- !validate_context(validation_context) && interactor.fail_on_invalid_context?
58
+ interactor.context_fail! unless validate_context(:called)
98
59
  end
99
60
 
100
61
  def validate_context(validation_context = nil)
101
62
  run_callbacks :validation do
102
- init_validation_context = context.validation_context
103
- context.validation_context = validation_context || init_validation_context
104
- context.valid?
105
- ensure
106
- context.validation_context = init_validation_context
63
+ interactor.context_valid?(validation_context)
107
64
  end
108
65
  end
109
66
  end
@@ -1,190 +1,206 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'active_support/core_ext/class/attribute'
4
+ require 'active_support/core_ext/string/inflections'
5
+
3
6
  module ActiveInteractor
4
- # The Base Interactor class inherited by all Interactor::Organizers
5
- #
7
+ # A base Organizer class. All organizers should inherit from
8
+ # {Organizer}.
6
9
  # @author Aaron Allen <hello@aaronmallen.me>
7
10
  # @since 0.0.1
8
- # @version 0.3
9
- class Organizer < ActiveInteractor::Base
11
+ # @!attribute [r] organized
12
+ # @!scope class
13
+ # @return [Array<Base>] the organized interactors
14
+ # @example a basic organizer
15
+ # class MyInteractor1 < ActiveInteractor::Base
16
+ # def perform
17
+ # context.interactor1 = true
18
+ # end
19
+ # end
20
+ #
21
+ # class MyInteractor2 < ActiveInteractor::Base
22
+ # def perform
23
+ # context.interactor2 = true
24
+ # end
25
+ # end
26
+ #
27
+ # class MyOrganizer < ActiveInteractor::Organizer
28
+ # organize MyInteractor1, MyInteractor2
29
+ # end
30
+ #
31
+ # MyOrganizer.perform
32
+ # #=> <MyOrganizer::Context interactor1=true interactor2=true>
33
+ class Organizer < Base
34
+ class_attribute :organized, instance_writer: false, default: []
10
35
  define_callbacks :each_perform
11
36
 
12
- class << self
13
- # Define a callback to call after each organized interactor's
14
- # {ActiveInteractor::Base.perform} has been invoked
15
- #
16
- # @example
17
- # class MyInteractor1 < ActiveInteractor::Base
18
- # before_perform :print_name
19
- #
20
- # def perform
21
- # puts 'MyInteractor1'
22
- # end
23
- # end
24
- #
25
- # class MyInteractor2 < ActiveInteractor::Base
26
- # before_perform :print_name
27
- #
28
- # def perform
29
- # puts 'MyInteractor2'
30
- # end
31
- # end
32
- #
33
- # class MyOrganizer < ActiveInteractor::Organizer
34
- # after_each_perform :print_done
35
- #
36
- # organized MyInteractor1, MyInteractor2
37
- #
38
- # private
39
- #
40
- # def print_done
41
- # puts "done"
42
- # end
43
- # end
44
- #
45
- # MyOrganizer.perform(name: 'Aaron')
46
- # "MyInteractor1"
47
- # "Done"
48
- # "MyInteractor2"
49
- # "Done"
50
- # #=> <MyOrganizer::Context name='Aaron'>
51
- def after_each_perform(*filters, &block)
52
- set_callback(:each_perform, :after, *filters, &block)
53
- end
54
-
55
- # Define a callback to call around each organized interactor's
56
- # {ActiveInteractor::Base.perform} has been invokation
57
- #
58
- # @example
59
- # class MyInteractor1 < ActiveInteractor::Base
60
- # before_perform :print_name
61
- #
62
- # def perform
63
- # puts 'MyInteractor1'
64
- # end
65
- # end
66
- #
67
- # class MyInteractor2 < ActiveInteractor::Base
68
- # before_perform :print_name
69
- #
70
- # def perform
71
- # puts 'MyInteractor2'
72
- # end
73
- # end
74
- #
75
- # class MyOrganizer < ActiveInteractor::Organizer
76
- # around_each_perform :print_time
77
- #
78
- # organized MyInteractor1, MyInteractor2
79
- #
80
- # private
81
- #
82
- # def print_time
83
- # puts Time.now.utc
84
- # yield
85
- # puts Time.now.utc
86
- # end
87
- # end
88
- #
89
- # MyOrganizer.perform(name: 'Aaron')
90
- # "2019-04-01 00:00:00 UTC"
91
- # "MyInteractor1"
92
- # "2019-04-01 00:00:01 UTC"
93
- # "2019-04-01 00:00:02 UTC"
94
- # "MyInteractor2"
95
- # "2019-04-01 00:00:03 UTC"
96
- # #=> <MyOrganizer::Context name='Aaron'>
97
- def around_each_perform(*filters, &block)
98
- set_callback(:each_perform, :around, *filters, &block)
99
- end
37
+ # Define a callback to call after each organized interactor's
38
+ # {Base.perform} has been invoked
39
+ # @example
40
+ # class MyInteractor1 < ActiveInteractor::Base
41
+ # before_perform :print_name
42
+ #
43
+ # def perform
44
+ # puts 'MyInteractor1'
45
+ # end
46
+ # end
47
+ #
48
+ # class MyInteractor2 < ActiveInteractor::Base
49
+ # before_perform :print_name
50
+ #
51
+ # def perform
52
+ # puts 'MyInteractor2'
53
+ # end
54
+ # end
55
+ #
56
+ # class MyOrganizer < ActiveInteractor::Organizer
57
+ # after_each_perform :print_done
58
+ #
59
+ # organized MyInteractor1, MyInteractor2
60
+ #
61
+ # private
62
+ #
63
+ # def print_done
64
+ # puts "done"
65
+ # end
66
+ # end
67
+ #
68
+ # MyOrganizer.perform(name: 'Aaron')
69
+ # "MyInteractor1"
70
+ # "Done"
71
+ # "MyInteractor2"
72
+ # "Done"
73
+ # #=> <MyOrganizer::Context name='Aaron'>
74
+ def self.after_each_perform(*filters, &block)
75
+ set_callback(:each_perform, :after, *filters, &block)
76
+ end
100
77
 
101
- # Define a callback to call before each organized interactor's
102
- # {ActiveInteractor::Base.perform} has been invoked
103
- #
104
- # @example
105
- # class MyInteractor1 < ActiveInteractor::Base
106
- # before_perform :print_name
107
- #
108
- # def perform
109
- # puts 'MyInteractor1'
110
- # end
111
- # end
112
- #
113
- # class MyInteractor2 < ActiveInteractor::Base
114
- # before_perform :print_name
115
- #
116
- # def perform
117
- # puts 'MyInteractor2'
118
- # end
119
- # end
120
- #
121
- # class MyOrganizer < ActiveInteractor::Organizer
122
- # before_each_perform :print_start
123
- #
124
- # organized MyInteractor1, MyInteractor2
125
- #
126
- # private
127
- #
128
- # def print_start
129
- # puts "Start"
130
- # end
131
- # end
132
- #
133
- # MyOrganizer.perform(name: 'Aaron')
134
- # "Start"
135
- # "MyInteractor1"
136
- # "Start"
137
- # "MyInteractor2"
138
- # #=> <MyOrganizer::Context name='Aaron'>
139
- def before_each_perform(*filters, &block)
140
- set_callback(:each_perform, :before, *filters, &block)
141
- end
78
+ # Define a callback to call around each organized interactor's
79
+ # {Base.perform} has been invokation
80
+ #
81
+ # @example
82
+ # class MyInteractor1 < ActiveInteractor::Base
83
+ # before_perform :print_name
84
+ #
85
+ # def perform
86
+ # puts 'MyInteractor1'
87
+ # end
88
+ # end
89
+ #
90
+ # class MyInteractor2 < ActiveInteractor::Base
91
+ # before_perform :print_name
92
+ #
93
+ # def perform
94
+ # puts 'MyInteractor2'
95
+ # end
96
+ # end
97
+ #
98
+ # class MyOrganizer < ActiveInteractor::Organizer
99
+ # around_each_perform :print_time
100
+ #
101
+ # organized MyInteractor1, MyInteractor2
102
+ #
103
+ # private
104
+ #
105
+ # def print_time
106
+ # puts Time.now.utc
107
+ # yield
108
+ # puts Time.now.utc
109
+ # end
110
+ # end
111
+ #
112
+ # MyOrganizer.perform(name: 'Aaron')
113
+ # "2019-04-01 00:00:00 UTC"
114
+ # "MyInteractor1"
115
+ # "2019-04-01 00:00:01 UTC"
116
+ # "2019-04-01 00:00:02 UTC"
117
+ # "MyInteractor2"
118
+ # "2019-04-01 00:00:03 UTC"
119
+ # #=> <MyOrganizer::Context name='Aaron'>
120
+ def self.around_each_perform(*filters, &block)
121
+ set_callback(:each_perform, :around, *filters, &block)
122
+ end
142
123
 
143
- # Declare Interactors to be invoked as part of the
144
- # organizer's invocation. These interactors are invoked in
145
- # the order in which they are declared
146
- #
147
- # @example
148
- # class MyFirstOrganizer < ActiveInteractor::Organizer
149
- # organize InteractorOne, InteractorTwo
150
- # end
151
- #
152
- # class MySecondOrganizer < ActiveInteractor::Organizer
153
- # organize [InteractorThree, InteractorFour]
154
- # end
155
- #
156
- # @param interactors [Array<ActiveInteractor::Base>] the interactors to call
157
- def organize(*interactors)
158
- @organized = interactors.flatten
159
- end
124
+ # Define a callback to call before each organized interactor's
125
+ # {Base.perform} has been invoked
126
+ #
127
+ # @example
128
+ # class MyInteractor1 < ActiveInteractor::Base
129
+ # before_perform :print_name
130
+ #
131
+ # def perform
132
+ # puts 'MyInteractor1'
133
+ # end
134
+ # end
135
+ #
136
+ # class MyInteractor2 < ActiveInteractor::Base
137
+ # before_perform :print_name
138
+ #
139
+ # def perform
140
+ # puts 'MyInteractor2'
141
+ # end
142
+ # end
143
+ #
144
+ # class MyOrganizer < ActiveInteractor::Organizer
145
+ # before_each_perform :print_start
146
+ #
147
+ # organized MyInteractor1, MyInteractor2
148
+ #
149
+ # private
150
+ #
151
+ # def print_start
152
+ # puts "Start"
153
+ # end
154
+ # end
155
+ #
156
+ # MyOrganizer.perform(name: 'Aaron')
157
+ # "Start"
158
+ # "MyInteractor1"
159
+ # "Start"
160
+ # "MyInteractor2"
161
+ # #=> <MyOrganizer::Context name='Aaron'>
162
+ def self.before_each_perform(*filters, &block)
163
+ set_callback(:each_perform, :before, *filters, &block)
164
+ end
160
165
 
161
- # An Array of declared Interactors to be invoked
162
- # @return [Array<ActiveInteractor::Base] the organized interactors
163
- def organized
164
- @organized ||= []
165
- end
166
+ # Declare Interactors to be invoked as part of the
167
+ # organizer's invocation. These interactors are invoked in
168
+ # the order in which they are declared
169
+ #
170
+ # @example
171
+ # class MyFirstOrganizer < ActiveInteractor::Organizer
172
+ # organize InteractorOne, InteractorTwo
173
+ # end
174
+ #
175
+ # class MySecondOrganizer < ActiveInteractor::Organizer
176
+ # organize [InteractorThree, InteractorFour]
177
+ # end
178
+ #
179
+ # @param interactors [Array<Base>] the interactors to call
180
+ def self.organize(*interactors)
181
+ self.organized = interactors.flatten.map do |interactor|
182
+ interactor.to_s.classify.safe_constantize
183
+ end.compact
166
184
  end
167
185
 
168
186
  # Invoke the organized interactors. An organizer is
169
- # expected not to define its own {ActiveInteractor::Interactor#perform #perform} method
187
+ # expected not to define its own {Base#perform} method
170
188
  # in favor of this default implementation.
171
189
  def perform
172
190
  self.class.organized.each do |interactor|
173
- execute_interactor_perform_with_callbacks!(interactor)
191
+ self.context = execute_interactor_perform_with_callbacks!(interactor)
174
192
  end
193
+ rescue Error::ContextFailure => e
194
+ self.context = e.context
195
+ ensure
196
+ self.context = self.class.context_class.new(context)
175
197
  end
176
198
 
177
199
  private
178
200
 
179
- def execute_interactor_perform!(interactor)
180
- self.context = interactor.new(context)
181
- .tap(&:skip_clean_context!)
182
- .execute_perform!
183
- end
184
-
185
201
  def execute_interactor_perform_with_callbacks!(interactor)
186
202
  run_callbacks :each_perform do
187
- execute_interactor_perform!(interactor)
203
+ interactor.new(context).execute_perform!
188
204
  end
189
205
  end
190
206
  end