Class: ActiveInteractor::Organizer

Inherits:
Base
  • Object
show all
Defined in:
lib/active_interactor/organizer.rb,
lib/active_interactor/organizer/interactor_interface.rb,
lib/active_interactor/organizer/interactor_interface_collection.rb

Overview

A base Organizer class. All organizers should inherit from Organizer.

Examples:

a basic organizer

class MyInteractor1 < ActiveInteractor::Base
  def perform
    context.interactor1 = true
  end
end

class MyInteractor2 < ActiveInteractor::Base
  def perform
    context.interactor2 = true
  end
end

class MyOrganizer < ActiveInteractor::Organizer
  organize MyInteractor1, MyInteractor2
end

MyOrganizer.perform
#=> <MyOrganizer::Context interactor1=true interactor2=true>

Author:

Since:

  • 0.0.1

Defined Under Namespace

Classes: InteractorInterface, InteractorInterfaceCollection

Class Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods inherited from Base

#dup

Methods included from Interactor

included, #options, #rollback, #with_options

Class Attribute Details

.parallelBoolean (readonly)

Returns whether or not to run the interactors in parallel.

Since:

  • 1.0.0


36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
# File 'lib/active_interactor/organizer.rb', line 36

class Organizer < Base
  class_attribute :parallel, instance_writer: false, default: false
  define_callbacks :each_perform

  # Define a callback to call after each organized interactor's
  #  {Base.perform} has been invoked
  # @example
  #  class MyInteractor1 < ActiveInteractor::Base
  #    before_perform :print_name
  #
  #    def perform
  #      puts 'MyInteractor1'
  #    end
  #  end
  #
  #  class MyInteractor2 < ActiveInteractor::Base
  #    before_perform :print_name
  #
  #    def perform
  #      puts 'MyInteractor2'
  #    end
  #  end
  #
  #  class MyOrganizer < ActiveInteractor::Organizer
  #    after_each_perform :print_done
  #
  #    organized MyInteractor1, MyInteractor2
  #
  #    private
  #
  #    def print_done
  #      puts "done"
  #    end
  #  end
  #
  #  MyOrganizer.perform(name: 'Aaron')
  #  "MyInteractor1"
  #  "Done"
  #  "MyInteractor2"
  #  "Done"
  #  #=> <MyOrganizer::Context name='Aaron'>
  def self.after_each_perform(*filters, &block)
    set_callback(:each_perform, :after, *filters, &block)
  end

  # Define a callback to call around each organized interactor's
  #  {Base.perform} has been invokation
  # @example
  #  class MyInteractor1 < ActiveInteractor::Base
  #    before_perform :print_name
  #
  #    def perform
  #      puts 'MyInteractor1'
  #    end
  #  end
  #
  #  class MyInteractor2 < ActiveInteractor::Base
  #    before_perform :print_name
  #
  #    def perform
  #      puts 'MyInteractor2'
  #    end
  #  end
  #
  #  class MyOrganizer < ActiveInteractor::Organizer
  #    around_each_perform :print_time
  #
  #    organized MyInteractor1, MyInteractor2
  #
  #    private
  #
  #    def print_time
  #      puts Time.now.utc
  #      yield
  #      puts Time.now.utc
  #    end
  #  end
  #
  #  MyOrganizer.perform(name: 'Aaron')
  #  "2019-04-01 00:00:00 UTC"
  #  "MyInteractor1"
  #  "2019-04-01 00:00:01 UTC"
  #  "2019-04-01 00:00:02 UTC"
  #  "MyInteractor2"
  #  "2019-04-01 00:00:03 UTC"
  #  #=> <MyOrganizer::Context name='Aaron'>
  def self.around_each_perform(*filters, &block)
    set_callback(:each_perform, :around, *filters, &block)
  end

  # Define a callback to call before each organized interactor's
  #  {Base.perform} has been invoked
  # @example
  #  class MyInteractor1 < ActiveInteractor::Base
  #    before_perform :print_name
  #
  #    def perform
  #      puts 'MyInteractor1'
  #    end
  #  end
  #
  #  class MyInteractor2 < ActiveInteractor::Base
  #    before_perform :print_name
  #
  #    def perform
  #      puts 'MyInteractor2'
  #    end
  #  end
  #
  #  class MyOrganizer < ActiveInteractor::Organizer
  #    before_each_perform :print_start
  #
  #    organized MyInteractor1, MyInteractor2
  #
  #    private
  #
  #    def print_start
  #      puts "Start"
  #    end
  #  end
  #
  #  MyOrganizer.perform(name: 'Aaron')
  #  "Start"
  #  "MyInteractor1"
  #  "Start"
  #  "MyInteractor2"
  #  #=> <MyOrganizer::Context name='Aaron'>
  def self.before_each_perform(*filters, &block)
    set_callback(:each_perform, :before, *filters, &block)
  end

  # Declare Interactors to be invoked as part of the
  #  organizer's invocation. These interactors are invoked in
  #  the order in which they are declared
  # @example Basic interactor organization
  #   class MyOrganizer < ActiveInteractor::Organizer
  #     organize :interactor_one, :interactor_two
  #   end
  # @example Conditional interactor organization with block
  #   class MyOrganizer < ActiveInteractor::Organizer
  #     organize do
  #       add :interactor_one
  #       add :interactor_two, if: -> { context.valid? }
  #     end
  #   end
  # @example Conditional interactor organization with method
  #   class MyOrganizer < ActiveInteractor::Organizer
  #     organize do
  #       add :interactor_one
  #       add :interactor_two, unless: :invalid_context?
  #     end
  #
  #     private
  #
  #     def invalid_context?
  #       !context.valid?
  #     end
  #   end
  # @example Interactor organization with perform options
  #   class MyOrganizer < ActiveInteractor::Organizer
  #     organize do
  #       add :interactor_one, validate: false
  #       add :interactor_two, skip_perform_callbacks: true
  #     end
  #   end
  # @param interactors [Array<Base|Symbol|String>] the interactors to call
  # @yield [.organized] if block given
  # @return [InteractorInterfaceCollection] an instance of {InteractorInterfaceCollection}
  def self.organize(*interactors, &block)
    organized.concat(interactors) if interactors
    organized.instance_eval(&block) if block
    organized
  end

  # Organized interactors
  # @return [InteractorInterfaceCollection] an instance of {InteractorInterfaceCollection}
  def self.organized
    @organized ||= InteractorInterfaceCollection.new
  end

  # Run organized interactors in parallel
  # @since 1.0.0
  def self.perform_in_parallel
    self.parallel = true
  end

  # Invoke the organized interactors. An organizer is
  #  expected not to define its own {Interactor#perform #perform} method
  #  in favor of this default implementation.
  def perform
    if self.class.parallel
      perform_in_parallel
    else
      perform_in_order
    end
  end

  private

  def execute_interactor(interface, fail_on_error = false, perform_options = {})
    interface.perform(self, context, fail_on_error, perform_options)
  end

  def execute_interactor_with_callbacks(interface, fail_on_error = false, perform_options = {})
    args = [interface, fail_on_error, perform_options]
    return execute_interactor(*args) if options.skip_each_perform_callbacks

    run_callbacks :each_perform do
      execute_interactor(*args)
    end
  end

  def merge_contexts(contexts)
    contexts.each { |context| @context.merge!(context) }
    context_fail! if contexts.any?(&:failure?)
  end

  def perform_in_order
    self.class.organized.each do |interface|
      result = execute_interactor_with_callbacks(interface, true)
      context.merge!(result) if result
    end
  rescue Error::ContextFailure => e
    context.merge!(e.context)
  end

  def perform_in_parallel
    results = self.class.organized.map do |interface|
      Thread.new { execute_interactor_with_callbacks(interface, false, skip_rollback: true) }
    end
    merge_contexts(results.map(&:value))
  end
end

Class Method Details

.after_each_perform(*filters, &block) ⇒ Object

Define a callback to call after each organized interactor's Interactor#perform has been invoked

Examples:

class MyInteractor1 < ActiveInteractor::Base
  before_perform :print_name

  def perform
    puts 'MyInteractor1'
  end
end

class MyInteractor2 < ActiveInteractor::Base
  before_perform :print_name

  def perform
    puts 'MyInteractor2'
  end
end

class MyOrganizer < ActiveInteractor::Organizer
  after_each_perform :print_done

  organized MyInteractor1, MyInteractor2

  private

  def print_done
    puts "done"
  end
end

MyOrganizer.perform(name: 'Aaron')
"MyInteractor1"
"Done"
"MyInteractor2"
"Done"
#=> <MyOrganizer::Context name='Aaron'>

Since:

  • 0.0.1


77
78
79
# File 'lib/active_interactor/organizer.rb', line 77

def self.after_each_perform(*filters, &block)
  set_callback(:each_perform, :after, *filters, &block)
end

.around_each_perform(*filters, &block) ⇒ Object

Define a callback to call around each organized interactor's Interactor#perform has been invokation

Examples:

class MyInteractor1 < ActiveInteractor::Base
  before_perform :print_name

  def perform
    puts 'MyInteractor1'
  end
end

class MyInteractor2 < ActiveInteractor::Base
  before_perform :print_name

  def perform
    puts 'MyInteractor2'
  end
end

class MyOrganizer < ActiveInteractor::Organizer
  around_each_perform :print_time

  organized MyInteractor1, MyInteractor2

  private

  def print_time
    puts Time.now.utc
    yield
    puts Time.now.utc
  end
end

MyOrganizer.perform(name: 'Aaron')
"2019-04-01 00:00:00 UTC"
"MyInteractor1"
"2019-04-01 00:00:01 UTC"
"2019-04-01 00:00:02 UTC"
"MyInteractor2"
"2019-04-01 00:00:03 UTC"
#=> <MyOrganizer::Context name='Aaron'>

Since:

  • 0.0.1


122
123
124
# File 'lib/active_interactor/organizer.rb', line 122

def self.around_each_perform(*filters, &block)
  set_callback(:each_perform, :around, *filters, &block)
end

.before_each_perform(*filters, &block) ⇒ Object

Define a callback to call before each organized interactor's Interactor#perform has been invoked

Examples:

class MyInteractor1 < ActiveInteractor::Base
  before_perform :print_name

  def perform
    puts 'MyInteractor1'
  end
end

class MyInteractor2 < ActiveInteractor::Base
  before_perform :print_name

  def perform
    puts 'MyInteractor2'
  end
end

class MyOrganizer < ActiveInteractor::Organizer
  before_each_perform :print_start

  organized MyInteractor1, MyInteractor2

  private

  def print_start
    puts "Start"
  end
end

MyOrganizer.perform(name: 'Aaron')
"Start"
"MyInteractor1"
"Start"
"MyInteractor2"
#=> <MyOrganizer::Context name='Aaron'>

Since:

  • 0.0.1


163
164
165
# File 'lib/active_interactor/organizer.rb', line 163

def self.before_each_perform(*filters, &block)
  set_callback(:each_perform, :before, *filters, &block)
end

.organize(*interactors) {|.organized| ... } ⇒ InteractorInterfaceCollection

Declare Interactors to be invoked as part of the organizer's invocation. These interactors are invoked in the order in which they are declared

Examples:

Basic interactor organization

class MyOrganizer < ActiveInteractor::Organizer
  organize :interactor_one, :interactor_two
end

Conditional interactor organization with block

class MyOrganizer < ActiveInteractor::Organizer
  organize do
    add :interactor_one
    add :interactor_two, if: -> { context.valid? }
  end
end

Conditional interactor organization with method

class MyOrganizer < ActiveInteractor::Organizer
  organize do
    add :interactor_one
    add :interactor_two, unless: :invalid_context?
  end

  private

  def invalid_context?
    !context.valid?
  end
end

Interactor organization with perform options

class MyOrganizer < ActiveInteractor::Organizer
  organize do
    add :interactor_one, validate: false
    add :interactor_two, skip_perform_callbacks: true
  end
end

Yields:

Since:

  • 0.0.1


204
205
206
207
208
# File 'lib/active_interactor/organizer.rb', line 204

def self.organize(*interactors, &block)
  organized.concat(interactors) if interactors
  organized.instance_eval(&block) if block
  organized
end

.organizedInteractorInterfaceCollection

Organized interactors

Since:

  • 0.0.1


212
213
214
# File 'lib/active_interactor/organizer.rb', line 212

def self.organized
  @organized ||= InteractorInterfaceCollection.new
end

.perform_in_parallelObject

Run organized interactors in parallel

Since:

  • 1.0.0


218
219
220
# File 'lib/active_interactor/organizer.rb', line 218

def self.perform_in_parallel
  self.parallel = true
end

Instance Method Details

#performObject

Invoke the organized interactors. An organizer is expected not to define its own #perform method in favor of this default implementation.

Since:

  • 0.0.1


225
226
227
228
229
230
231
# File 'lib/active_interactor/organizer.rb', line 225

def perform
  if self.class.parallel
    perform_in_parallel
  else
    perform_in_order
  end
end