interactor-extended 0.1.0
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 +7 -0
- data/.rspec +2 -0
- data/.rubocop.yml +28 -0
- data/CHANGELOG.md +5 -0
- data/CODE_OF_CONDUCT.md +84 -0
- data/LICENSE.txt +21 -0
- data/README.md +86 -0
- data/Rakefile +12 -0
- data/VERSION +1 -0
- data/docs/colorize.md +22 -0
- data/docs/configuration.md +63 -0
- data/docs/context_definition.md +21 -0
- data/docs/duration.md +37 -0
- data/docs/flow.md +25 -0
- data/docs/jobify.md +38 -0
- data/docs/light_context.md +24 -0
- data/docs/light_context_definition.md +19 -0
- data/docs/loggable.md +17 -0
- data/docs/operation.md +34 -0
- data/docs/organize.md +20 -0
- data/docs/populate.md +34 -0
- data/lib/interactor/colorize.rb +61 -0
- data/lib/interactor/context_definition.rb +156 -0
- data/lib/interactor/contextable.rb +18 -0
- data/lib/interactor/duration/base_formatter.rb +32 -0
- data/lib/interactor/duration/color_string_formatter.rb +22 -0
- data/lib/interactor/duration/json_formatter.rb +43 -0
- data/lib/interactor/duration/string_formatter.rb +57 -0
- data/lib/interactor/duration.rb +97 -0
- data/lib/interactor/extended/configuration.rb +51 -0
- data/lib/interactor/extended/error.rb +8 -0
- data/lib/interactor/extended/helpers.rb +54 -0
- data/lib/interactor/extended/version.rb +30 -0
- data/lib/interactor/extended.rb +84 -0
- data/lib/interactor/flow.rb +21 -0
- data/lib/interactor/jobify.rb +121 -0
- data/lib/interactor/light_context.rb +169 -0
- data/lib/interactor/light_context_definition.rb +53 -0
- data/lib/interactor/loggable.rb +28 -0
- data/lib/interactor/operation.rb +21 -0
- data/lib/interactor/organize.rb +49 -0
- data/lib/interactor/populate.rb +63 -0
- data/lib/interactor/threadable.rb +13 -0
- data/sig/interactor/colorize.rbs +9 -0
- data/sig/interactor/context_definition.rbs +20 -0
- data/sig/interactor/contextable.rbs +6 -0
- data/sig/interactor/duration/base_formatter.rbs +12 -0
- data/sig/interactor/duration/color_string_formatter.rbs +9 -0
- data/sig/interactor/duration/json_formatter.rbs +7 -0
- data/sig/interactor/duration/string_formatter.rbs +7 -0
- data/sig/interactor/duration.rbs +7 -0
- data/sig/interactor/extended/configuration.rbs +14 -0
- data/sig/interactor/extended/error.rbs +6 -0
- data/sig/interactor/extended/helpers.rbs +9 -0
- data/sig/interactor/extended/version.rbs +10 -0
- data/sig/interactor/extended.rbs +12 -0
- data/sig/interactor/flow.rbs +8 -0
- data/sig/interactor/jobify.rbs +32 -0
- data/sig/interactor/light_context.rbs +28 -0
- data/sig/interactor/light_context_definition.rbs +13 -0
- data/sig/interactor/loggable.rbs +13 -0
- data/sig/interactor/operation.rbs +8 -0
- data/sig/interactor/organize.rbs +21 -0
- data/sig/interactor/populate.rbs +20 -0
- data/sig/interactor/threadable.rbs +5 -0
- metadata +127 -0
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'interactor'
|
|
4
|
+
require 'logger'
|
|
5
|
+
require_relative 'extended/version'
|
|
6
|
+
require_relative 'extended/helpers'
|
|
7
|
+
require_relative 'extended/configuration'
|
|
8
|
+
require_relative 'contextable'
|
|
9
|
+
require_relative 'light_context'
|
|
10
|
+
require_relative 'light_context_definition'
|
|
11
|
+
require_relative 'context_definition'
|
|
12
|
+
require_relative 'organize'
|
|
13
|
+
require_relative 'populate'
|
|
14
|
+
require_relative 'loggable'
|
|
15
|
+
require_relative 'threadable'
|
|
16
|
+
require_relative 'duration/base_formatter'
|
|
17
|
+
require_relative 'duration/json_formatter'
|
|
18
|
+
require_relative 'duration/string_formatter'
|
|
19
|
+
require_relative 'duration/color_string_formatter'
|
|
20
|
+
require_relative 'duration'
|
|
21
|
+
require_relative 'jobify'
|
|
22
|
+
require_relative 'colorize'
|
|
23
|
+
require_relative 'flow'
|
|
24
|
+
require_relative 'operation'
|
|
25
|
+
|
|
26
|
+
module Interactor
|
|
27
|
+
module Extended
|
|
28
|
+
@cache = {}
|
|
29
|
+
class << self
|
|
30
|
+
# Returns the current configuration.
|
|
31
|
+
# @return [Configuration]
|
|
32
|
+
def configuration
|
|
33
|
+
@configuration ||= Configuration.new
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Sets the configuration.
|
|
37
|
+
# @param config [Configuration] The new configuration.
|
|
38
|
+
# @return [Configuration]
|
|
39
|
+
attr_writer :configuration
|
|
40
|
+
|
|
41
|
+
# Yields the configuration for customization.
|
|
42
|
+
# @yield [Configuration] The configuration object.
|
|
43
|
+
# @return [untyped]
|
|
44
|
+
def configure
|
|
45
|
+
yield configuration
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Returns the modules to include for a given kind and type.
|
|
49
|
+
# @param kind [Symbol] The kind of module (:flow or :operation).
|
|
50
|
+
# @param type [Symbol, Array<Symbol>] The type or types of modules.
|
|
51
|
+
# @return [Array<Module>]
|
|
52
|
+
def modules(kind, type)
|
|
53
|
+
[
|
|
54
|
+
kind == :flow ? ::Interactor::Organizer : ::Interactor,
|
|
55
|
+
*(type.is_a?(Array) ? type : configuration.types[type].to_a)
|
|
56
|
+
]
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Builds a module containing the specified kinds and types.
|
|
60
|
+
# @param kind [Symbol] The kind of module.
|
|
61
|
+
# @param type [Symbol, Array<Symbol>] The type or types of modules.
|
|
62
|
+
# @return [Module]
|
|
63
|
+
def build_module(kind, type)
|
|
64
|
+
modules = modules(kind, type)
|
|
65
|
+
@cache[[kind, type]] ||= Module.new do
|
|
66
|
+
define_singleton_method(:included) do |base|
|
|
67
|
+
modules.each { base.include(_1) }
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# Spawns a new thread that inherits the current duration tracking context.
|
|
73
|
+
# @yield The block to execute in the new thread.
|
|
74
|
+
# @return [Thread]
|
|
75
|
+
def thread(&block)
|
|
76
|
+
current = Thread.current.thread_variable_get('interactor.duration')&.data
|
|
77
|
+
Thread.new do
|
|
78
|
+
Thread.current.thread_variable_set('interactor.duration_current', current)
|
|
79
|
+
block.call
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Interactor
|
|
4
|
+
# Provides flow management for interactors.
|
|
5
|
+
module Flow
|
|
6
|
+
class << self
|
|
7
|
+
# Includes the flow modules into a base class.
|
|
8
|
+
# @param base [Class] The class to include the module into.
|
|
9
|
+
def included(base)
|
|
10
|
+
Extended.modules(:flow, ::Interactor::Extended.configuration.type).each { base.include(_1) }
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
# Builds a flow module for the given type.
|
|
14
|
+
# @param type [Symbol] The type of flow.
|
|
15
|
+
# @return [Module]
|
|
16
|
+
def [](type)
|
|
17
|
+
Extended.build_module(:flow, type)
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Interactor
|
|
4
|
+
# Provides jobify functionality to interactor classes, allowing them to be executed as background jobs.
|
|
5
|
+
module Jobify
|
|
6
|
+
include Interactor::Extended::Helpers
|
|
7
|
+
|
|
8
|
+
class << self
|
|
9
|
+
def included(base)
|
|
10
|
+
base.extend(ClassMethods)
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
module ClassMethods
|
|
15
|
+
attr_reader :job_active, :job_class, :job_default, :job_params
|
|
16
|
+
|
|
17
|
+
# Configures the interactor to be jobified.
|
|
18
|
+
# @param klass [Class, nil] The job class to use. Defaults to `#{self.class.name}Job`.
|
|
19
|
+
# @param params [untyped, nil] Parameters to pass to the job. Can be a Proc.
|
|
20
|
+
# @param default [bool] Whether jobify is enabled by default.
|
|
21
|
+
# @yield [interactor] Yields the interactor instance to generate params.
|
|
22
|
+
def jobify(klass: nil, params: nil, default: false, &block)
|
|
23
|
+
@job_active = true
|
|
24
|
+
@job_class = klass
|
|
25
|
+
@job_default = default
|
|
26
|
+
@job_params = params || block
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Returns whether the job is active.
|
|
31
|
+
# @return [bool, nil]
|
|
32
|
+
def job_active
|
|
33
|
+
self.class.job_active
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Returns whether the job is enabled by default.
|
|
37
|
+
# @return [bool]
|
|
38
|
+
def job_default
|
|
39
|
+
self.class.job_default
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Runs the interactor, potentially as a job.
|
|
43
|
+
# @return [untyped]
|
|
44
|
+
def run!
|
|
45
|
+
return super if !jobify? || !job_active
|
|
46
|
+
|
|
47
|
+
with_hooks { perform_job(job_params) }
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
protected
|
|
51
|
+
|
|
52
|
+
# Determines if jobify should be used based on context or default.
|
|
53
|
+
# @return [bool]
|
|
54
|
+
def jobify?
|
|
55
|
+
context[:jobify].nil? ? job_default : context[:jobify]
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Returns the job class.
|
|
59
|
+
# @return [Class]
|
|
60
|
+
def job_class
|
|
61
|
+
self.class.job_class || Object.const_get("#{self.class.name}Job")
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Returns the job parameters.
|
|
65
|
+
# @return [untyped]
|
|
66
|
+
def job_params
|
|
67
|
+
self.class.job_params&.call(self)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# Checks if the job should be performed with a delay.
|
|
71
|
+
# @return [bool]
|
|
72
|
+
def perform_in?
|
|
73
|
+
context[:jobify].to_s.to_i.positive?
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Performs the job with the given parameters.
|
|
77
|
+
# @param params [untyped] The parameters for the job.
|
|
78
|
+
# @return [void]
|
|
79
|
+
def perform_job(params = nil)
|
|
80
|
+
args = (params || context.to_h.reject { |key| key.match(/^_/) }).then do |parameters|
|
|
81
|
+
parameters.respond_to?(:as_json) ? parameters.as_json : deep_transform_keys(parameters, &:to_s)
|
|
82
|
+
end
|
|
83
|
+
perform_in? ? perform_in(context[:jobify].to_i, args) : perform_later(args)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# Performs the job later (asynchronously).
|
|
87
|
+
# @param args [untyped] The arguments for the job.
|
|
88
|
+
# @return [void]
|
|
89
|
+
def perform_later(args)
|
|
90
|
+
return job_class.perform_later(args) if job_class.respond_to?(:perform_later)
|
|
91
|
+
|
|
92
|
+
job_class.perform_async(args) if sidekiq?
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# Performs the job in with a delay.
|
|
96
|
+
# @param wait [untyped] The delay duration.
|
|
97
|
+
# @param args [untyped] The arguments for the job.
|
|
98
|
+
# @return [void]
|
|
99
|
+
def perform_in(wait, args)
|
|
100
|
+
return job_class.perform_in(wait, args) if job_class.respond_to?(:perform_in)
|
|
101
|
+
|
|
102
|
+
job_class.set(wait:).perform_later(args) if application_job?
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# Checks if jos class is Sidekiq.
|
|
106
|
+
# @return [bool]
|
|
107
|
+
def sidekiq?
|
|
108
|
+
SidekiqJob.included_modules.include?(Sidekiq::Job)
|
|
109
|
+
rescue StandardError
|
|
110
|
+
false
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# Checks if job class is ApplicationJob.
|
|
114
|
+
# @return [bool]
|
|
115
|
+
def application_job?
|
|
116
|
+
ApplicationJob.superclass == ActiveJob::Base
|
|
117
|
+
rescue StandardError
|
|
118
|
+
false
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
end
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Interactor
|
|
4
|
+
# A lightweight context object for interactors.
|
|
5
|
+
class LightContext < BasicObject
|
|
6
|
+
%i[
|
|
7
|
+
block_given?
|
|
8
|
+
class
|
|
9
|
+
hash
|
|
10
|
+
instance_of?
|
|
11
|
+
instance_variables
|
|
12
|
+
is_a?
|
|
13
|
+
kind_of?
|
|
14
|
+
method
|
|
15
|
+
methods
|
|
16
|
+
nil?
|
|
17
|
+
object_id
|
|
18
|
+
private_methods
|
|
19
|
+
public_send
|
|
20
|
+
send
|
|
21
|
+
tap
|
|
22
|
+
then
|
|
23
|
+
raise
|
|
24
|
+
itself
|
|
25
|
+
].each do |method_name|
|
|
26
|
+
define_method(method_name, ::Kernel.instance_method(method_name))
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Checks if the context is successful.
|
|
30
|
+
# @return [bool]
|
|
31
|
+
def success?
|
|
32
|
+
!failure?
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Checks if the context has failed.
|
|
36
|
+
# @return [bool]
|
|
37
|
+
def failure?
|
|
38
|
+
@failure || false
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Fails the context with the given data.
|
|
42
|
+
# @param context [Hash] The failure data.
|
|
43
|
+
# @raise [Failure] Always raises a Failure.
|
|
44
|
+
def fail!(context = {})
|
|
45
|
+
context.each { |key, value| self[key.to_sym] = value }
|
|
46
|
+
@failure = true
|
|
47
|
+
raise Failure, self
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Marks an interactor as called.
|
|
51
|
+
# @param interactor [untyped] The interactor instance.
|
|
52
|
+
def called!(interactor)
|
|
53
|
+
_called << interactor
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Triggers rollback for all called interactors.
|
|
57
|
+
# @return [bool]
|
|
58
|
+
def rollback!
|
|
59
|
+
return false if @rolled_back
|
|
60
|
+
|
|
61
|
+
_called.reverse_each(&:rollback)
|
|
62
|
+
@rolled_back = true
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Returns the list of interactors called so far (used for rollback).
|
|
66
|
+
# @return [Array<untyped>]
|
|
67
|
+
def _called
|
|
68
|
+
@called ||= []
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Initializes the context with data.
|
|
72
|
+
# @param data [Hash] The initial data.
|
|
73
|
+
def initialize(data = {})
|
|
74
|
+
@data = data.to_h.transform_keys(&:to_sym)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Converts the context to a hash.
|
|
78
|
+
# @return [Hash]
|
|
79
|
+
def to_h
|
|
80
|
+
data.to_h.clone
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# Returns a string representation of the context.
|
|
84
|
+
# @return [String]
|
|
85
|
+
def inspect
|
|
86
|
+
"#<#{self.class.name} #{to_h}>"
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# Returns a string representation of the context.
|
|
90
|
+
# @return [String]
|
|
91
|
+
def to_s
|
|
92
|
+
inspect
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def pretty_print(pp)
|
|
96
|
+
pp.text(inspect)
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# Checks if the context contains a key.
|
|
100
|
+
# @param name [untyped] The key to check.
|
|
101
|
+
# @return [bool]
|
|
102
|
+
def key?(name)
|
|
103
|
+
data.key?(name.to_sym)
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# Retrieves a value by key.
|
|
107
|
+
# @param name [untyped] The key.
|
|
108
|
+
# @return [untyped]
|
|
109
|
+
def [](name)
|
|
110
|
+
data[name.to_sym]
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# Sets a value by key.
|
|
114
|
+
# @param key [untyped] The key.
|
|
115
|
+
# @param value [untyped] The value.
|
|
116
|
+
# @return [untyped]
|
|
117
|
+
def []=(key, value)
|
|
118
|
+
data[key.to_sym] = value
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# Checks if the context responds to a method.
|
|
122
|
+
# @param name [untyped] The method name.
|
|
123
|
+
# @param include_private [bool] Whether to include private methods.
|
|
124
|
+
# @return [bool]
|
|
125
|
+
def respond_to?(name, include_private = false) # rubocop:disable Style/OptionalBooleanParameter
|
|
126
|
+
self.class.method_defined?(name) || respond_to_missing?(name, include_private)
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
# Deconstructs the context for pattern matching.
|
|
130
|
+
# @param keys [Array<Symbol>, nil] The keys to include.
|
|
131
|
+
# @return [Hash[Symbol, untyped]]
|
|
132
|
+
def deconstruct_keys(keys = nil)
|
|
133
|
+
deconstructed_keys = to_h.merge(success: success?, failure: failure?)
|
|
134
|
+
keys ? deconstructed_keys.slice(*keys) : deconstructed_keys
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
private
|
|
138
|
+
|
|
139
|
+
attr_reader :data
|
|
140
|
+
|
|
141
|
+
def respond_to_missing?(name, _include_private = false)
|
|
142
|
+
return true if name.end_with?('=')
|
|
143
|
+
return true if name.end_with?('?') && data.key?(name.to_s.chomp('?').to_sym)
|
|
144
|
+
return true if data.key?(name.to_sym)
|
|
145
|
+
|
|
146
|
+
false
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def method_missing(name, *args)
|
|
150
|
+
if name.end_with?('?') && data.key?(key = name.to_s.chomp('?').to_sym)
|
|
151
|
+
value = data[key]
|
|
152
|
+
value.respond_to?(:empty?) ? !value.empty? : !!value # rubocop:disable Style/DoubleNegation
|
|
153
|
+
elsif name.end_with?('=')
|
|
154
|
+
data[name.to_s.chomp('=').to_sym] = args.first
|
|
155
|
+
else
|
|
156
|
+
data[name]
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
class << self
|
|
161
|
+
# Builds a LightContext from a hash or returns the context itself.
|
|
162
|
+
# @param context [Hash, LightContext] The context data.
|
|
163
|
+
# @return [LightContext]
|
|
164
|
+
def build(context = {})
|
|
165
|
+
context.is_a?(self) ? context : new(context.to_h)
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
end
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Interactor
|
|
4
|
+
# Defines inputs and outputs for a light context interactor.
|
|
5
|
+
module LightContextDefinition
|
|
6
|
+
include Interactor::Extended::Helpers
|
|
7
|
+
|
|
8
|
+
class << self
|
|
9
|
+
def included(base)
|
|
10
|
+
base.extend(ClassMethods)
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
module ClassMethods
|
|
15
|
+
# Defines an attribute for the context.
|
|
16
|
+
# @param methods [Symbol, String] The attribute names.
|
|
17
|
+
# @param array [bool] Whether the attribute is an array.
|
|
18
|
+
# @param default [untyped, nil] Default value.
|
|
19
|
+
# @param required [bool] Whether the attribute is required.
|
|
20
|
+
# @param direction [Symbol] The direction (:input or :output).
|
|
21
|
+
def attribute(*methods, array: false, default: nil, required: false, direction: :output) # rubocop:disable Lint/UnusedMethodArgument
|
|
22
|
+
methods.pop if !(methods.last.is_a?(String) || methods.last.is_a?(Symbol)) || methods.last == :boolean
|
|
23
|
+
methods.each do |method|
|
|
24
|
+
define_method(method) { context[method] }
|
|
25
|
+
end
|
|
26
|
+
return if direction != :output
|
|
27
|
+
|
|
28
|
+
methods.each do |method|
|
|
29
|
+
define_method(:"#{method}=") { context[method] = _1 }
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Defines an input attribute.
|
|
34
|
+
# @param methods [Symbol, String] The input names.
|
|
35
|
+
# @param array [bool] Whether the input is an array.
|
|
36
|
+
# @param default [untyped, nil] Default value.
|
|
37
|
+
# @param required [bool] Whether the input is required.
|
|
38
|
+
# @param writer [bool] Whether to define a writer method.
|
|
39
|
+
def input(*methods, array: false, default: nil, required: false, writer: false)
|
|
40
|
+
attribute(*methods, array:, default:, required:, direction: writer ? :output : :input)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Defines an output attribute.
|
|
44
|
+
# @param methods [Symbol, String] The output names.
|
|
45
|
+
# @param array [bool] Whether the output is an array.
|
|
46
|
+
# @param default [untyped, nil] Default value.
|
|
47
|
+
# @param required [bool] Whether the output is required.
|
|
48
|
+
def output(*methods, array: false, default: nil, required: false)
|
|
49
|
+
attribute(*methods, array:, default:, required:)
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Interactor
|
|
4
|
+
# Provides logging functionality for interactors.
|
|
5
|
+
module Loggable
|
|
6
|
+
class << self
|
|
7
|
+
# Includes the logging functionality into a base class.
|
|
8
|
+
# @param base [Class] The class to include the module into.
|
|
9
|
+
def included(base)
|
|
10
|
+
base.extend(ClassMethods)
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
module ClassMethods
|
|
15
|
+
# Returns the logger from the configuration.
|
|
16
|
+
# @return [untyped]
|
|
17
|
+
def logger
|
|
18
|
+
::Interactor::Extended.configuration.logger
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Returns the logger.
|
|
23
|
+
# @return [untyped]
|
|
24
|
+
def logger
|
|
25
|
+
self.class.logger
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Interactor
|
|
4
|
+
# Provides operation management for interactors.
|
|
5
|
+
module Operation
|
|
6
|
+
class << self
|
|
7
|
+
# Includes the operation modules into a base class.
|
|
8
|
+
# @param base [Class] The class to include the module into.
|
|
9
|
+
def included(base)
|
|
10
|
+
Extended.modules(:operation, ::Interactor::Extended.configuration.type).each { base.include(_1) }
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
# Builds an operation module for the given type.
|
|
14
|
+
# @param type [Symbol] The type of operation.
|
|
15
|
+
# @return [Module]
|
|
16
|
+
def [](type)
|
|
17
|
+
Extended.build_module(:operation, type)
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Interactor
|
|
4
|
+
# Provides orchestration functionality for interactors.
|
|
5
|
+
module Organize
|
|
6
|
+
# A thread runner that executes a klass in a new thread.
|
|
7
|
+
class ThreadRunner < Proc
|
|
8
|
+
alias call! call
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
class << self
|
|
12
|
+
# Includes the organize functionality into a base class.
|
|
13
|
+
# @param base [Class] The class to include the module into.
|
|
14
|
+
def included(base)
|
|
15
|
+
base.extend(ClassMethods)
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
module ClassMethods
|
|
20
|
+
# Creates a ThreadRunner for the given class.
|
|
21
|
+
# @param klass [Class] The class to run in a thread.
|
|
22
|
+
# @return [ThreadRunner]
|
|
23
|
+
def thread(klass)
|
|
24
|
+
ThreadRunner.new { |context| ::Interactor::Extended.thread { klass.call!(context) } }
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Executes the organized interactors.
|
|
29
|
+
# @return [untyped]
|
|
30
|
+
def call
|
|
31
|
+
organize(organized) if self.class.respond_to?(:organized)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
protected
|
|
35
|
+
|
|
36
|
+
# Organizes the execution of interactors.
|
|
37
|
+
# @param organized [untyped] The interactors to organize.
|
|
38
|
+
# @return [untyped]
|
|
39
|
+
def organize(*organized)
|
|
40
|
+
organized.flatten.map { _1.call!(context) }.map { _1.join if _1.is_a?(Thread) }
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Returns the organized interactors.
|
|
44
|
+
# @return [untyped]
|
|
45
|
+
def organized
|
|
46
|
+
context[:organized] || self.class.organized
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Interactor
|
|
4
|
+
# Provides context population functionality.
|
|
5
|
+
module Populate
|
|
6
|
+
class << self
|
|
7
|
+
# Includes the populate functionality into a base class.
|
|
8
|
+
# @param base [Class] The class to include the module into.
|
|
9
|
+
def included(base)
|
|
10
|
+
base.extend(ClassMethods)
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
module ClassMethods
|
|
15
|
+
# Calls the interactor with the given arguments.
|
|
16
|
+
# @param args [untyped] The arguments.
|
|
17
|
+
# @return [untyped]
|
|
18
|
+
def call(*args)
|
|
19
|
+
populate(*args) { super(_1) }
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Calls the interactor with the given arguments, raising on failure.
|
|
23
|
+
# @param args [untyped] The arguments.
|
|
24
|
+
# @return [untyped]
|
|
25
|
+
def call!(*args)
|
|
26
|
+
populate(*args) { super(_1) }
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
private
|
|
30
|
+
|
|
31
|
+
# Populates the context with data from the parent context.
|
|
32
|
+
# @param context [Hash] The context to populate.
|
|
33
|
+
# @param parent_context [untyped] The parent context.
|
|
34
|
+
# @param keys [untyped] The keys to populate.
|
|
35
|
+
# @yield [context] The context to yield.
|
|
36
|
+
# @return [untyped]
|
|
37
|
+
def populate(context = {}, parent_context = nil, *keys) # rubocop:disable Metrics
|
|
38
|
+
parent_context = context[:context] if context[:context]
|
|
39
|
+
keys = context[:context_keys] if context[:context_keys]
|
|
40
|
+
return unless block_given?
|
|
41
|
+
return yield(context) if parent_context.nil?
|
|
42
|
+
|
|
43
|
+
original_keys = keys
|
|
44
|
+
keys = parent_context.to_h.keys.grep(/^_/) if original_keys.empty?
|
|
45
|
+
keys&.each { context[_1] = parent_context[_1] }
|
|
46
|
+
yield(context).tap do |result|
|
|
47
|
+
keys = result.to_h.keys.grep(/^_/) if original_keys.empty?
|
|
48
|
+
keys.each { parent_context[_1] = result[_1] }
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
protected
|
|
54
|
+
|
|
55
|
+
# Assigns data to the context.
|
|
56
|
+
# @param data [Hash] The data to assign.
|
|
57
|
+
# @return [LightContext]
|
|
58
|
+
def context_assign(data = {})
|
|
59
|
+
data.each { |key, value| context[key] = value }
|
|
60
|
+
context
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Interactor
|
|
4
|
+
# Provides a thread helper that propagates duration tracking context into new threads.
|
|
5
|
+
module Threadable
|
|
6
|
+
# Spawns a new thread that inherits the current duration tracking context.
|
|
7
|
+
# @yield The block to execute in the new thread.
|
|
8
|
+
# @return [Thread]
|
|
9
|
+
def thread(&)
|
|
10
|
+
Interactor::Extended.thread(&)
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
module Interactor
|
|
2
|
+
module ContextDefinition
|
|
3
|
+
class << self
|
|
4
|
+
def included: (untyped base) -> void
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
module ClassMethods
|
|
8
|
+
attr_accessor attributes: Hash[Symbol, Hash[Symbol, untyped]] | nil
|
|
9
|
+
|
|
10
|
+
def attribute: (*Symbol | String, ?array: bool, ?default: untyped, ?required: bool, ?direction: Symbol) -> void
|
|
11
|
+
def input: (*Symbol | String, ?array: bool, ?default: untyped, ?required: bool, ?writer: bool) -> void
|
|
12
|
+
def output: (*Symbol | String, ?array: bool, ?default: untyped, ?required: bool) -> void
|
|
13
|
+
def all_attributes: () -> Hash[Symbol, Hash[Symbol, untyped]]
|
|
14
|
+
def remove_all_attributes!: () -> void
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def initialize: (*untyped) -> void
|
|
18
|
+
def run!: () -> untyped
|
|
19
|
+
end
|
|
20
|
+
end
|