activeinteractor 0.1.7 → 1.0.0.beta.1

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