interactify 0.1.0.pre.alpha.1 → 0.3.0.pre.alpha.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.
- checksums.yaml +4 -4
- data/.rubocop.yml +23 -0
- data/CHANGELOG.md +9 -1
- data/README.md +174 -105
- data/lib/interactify/async_job_klass.rb +61 -0
- data/lib/interactify/call_wrapper.rb +2 -0
- data/lib/interactify/contract_failure.rb +6 -0
- data/lib/interactify/contract_helpers.rb +10 -10
- data/lib/interactify/dsl.rb +6 -2
- data/lib/interactify/each_chain.rb +12 -4
- data/lib/interactify/if_interactor.rb +10 -4
- data/lib/interactify/interactor_wiring/callable_representation.rb +79 -0
- data/lib/interactify/interactor_wiring/constants.rb +125 -0
- data/lib/interactify/interactor_wiring/error_context.rb +41 -0
- data/lib/interactify/interactor_wiring/files.rb +51 -0
- data/lib/interactify/interactor_wiring.rb +57 -272
- data/lib/interactify/interactor_wrapper.rb +72 -0
- data/lib/interactify/job_maker.rb +20 -69
- data/lib/interactify/jobable.rb +9 -7
- data/lib/interactify/mismatching_promise_error.rb +17 -0
- data/lib/interactify/organizer.rb +30 -0
- data/lib/interactify/promising.rb +34 -0
- data/lib/interactify/rspec/matchers.rb +9 -11
- data/lib/interactify/unique_klass_name.rb +21 -0
- data/lib/interactify/version.rb +1 -1
- data/lib/interactify.rb +39 -9
- metadata +14 -3
- data/lib/interactify/organizer_call_monkey_patch.rb +0 -40
@@ -0,0 +1,79 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "interactify/interactor_wiring/error_context"
|
4
|
+
|
5
|
+
module Interactify
|
6
|
+
class InteractorWiring
|
7
|
+
class CallableRepresentation
|
8
|
+
attr_reader :filename, :klass, :wiring
|
9
|
+
|
10
|
+
delegate :interactor_lookup, to: :wiring
|
11
|
+
|
12
|
+
def initialize(filename:, klass:, wiring:)
|
13
|
+
@filename = filename
|
14
|
+
@klass = klass
|
15
|
+
@wiring = wiring
|
16
|
+
end
|
17
|
+
|
18
|
+
def validate_callable(error_context: ErrorContext.new)
|
19
|
+
if organizer?
|
20
|
+
assign_previously_defined(error_context:)
|
21
|
+
validate_children(error_context:)
|
22
|
+
end
|
23
|
+
|
24
|
+
validate_self(error_context:)
|
25
|
+
end
|
26
|
+
|
27
|
+
def expected_keys
|
28
|
+
klass.respond_to?(:expected_keys) ? Array(klass.expected_keys) : []
|
29
|
+
end
|
30
|
+
|
31
|
+
def promised_keys
|
32
|
+
klass.respond_to?(:promised_keys) ? Array(klass.promised_keys) : []
|
33
|
+
end
|
34
|
+
|
35
|
+
def all_keys
|
36
|
+
expected_keys.concat(promised_keys)
|
37
|
+
end
|
38
|
+
|
39
|
+
def inspect
|
40
|
+
"#<#{self.class.name}#{object_id} @filename=#{filename}, @klass=#{klass.name}>"
|
41
|
+
end
|
42
|
+
|
43
|
+
def organizer?
|
44
|
+
klass.respond_to?(:organized) && klass.organized.any?
|
45
|
+
end
|
46
|
+
|
47
|
+
def assign_previously_defined(error_context:)
|
48
|
+
return unless contract?
|
49
|
+
|
50
|
+
error_context.append_previously_defined_keys(all_keys)
|
51
|
+
end
|
52
|
+
|
53
|
+
def validate_children(error_context:)
|
54
|
+
klass.organized.each do |interactor|
|
55
|
+
interactor_as_callable = interactor_lookup[interactor]
|
56
|
+
next if interactor_as_callable.nil?
|
57
|
+
|
58
|
+
error_context = interactor_as_callable.validate_callable(error_context:)
|
59
|
+
end
|
60
|
+
|
61
|
+
error_context
|
62
|
+
end
|
63
|
+
|
64
|
+
private
|
65
|
+
|
66
|
+
def contract?
|
67
|
+
klass.ancestors.include? Interactor::Contracts
|
68
|
+
end
|
69
|
+
|
70
|
+
def validate_self(error_context:)
|
71
|
+
return error_context unless contract?
|
72
|
+
|
73
|
+
error_context.infer_missing_keys(self)
|
74
|
+
error_context.add_promised_keys(promised_keys)
|
75
|
+
error_context
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,125 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Interactify
|
4
|
+
class InteractorWiring
|
5
|
+
class Constants
|
6
|
+
attr_reader :root, :organizer_files, :interactor_files
|
7
|
+
|
8
|
+
def initialize(root:, organizer_files:, interactor_files:)
|
9
|
+
@root = root.is_a?(Pathname) ? root : Pathname.new(root)
|
10
|
+
@organizer_files = organizer_files
|
11
|
+
@interactor_files = interactor_files
|
12
|
+
end
|
13
|
+
|
14
|
+
def organizers
|
15
|
+
@organizers ||= organizer_files.flat_map do |f|
|
16
|
+
callables_in_file(f)
|
17
|
+
end.compact.select(&:organizer?)
|
18
|
+
end
|
19
|
+
|
20
|
+
def interactors
|
21
|
+
@interactors ||= interactor_files.flat_map do |f|
|
22
|
+
callables_in_file(f)
|
23
|
+
end.compact.reject(&:organizer?)
|
24
|
+
end
|
25
|
+
|
26
|
+
def interactor_lookup
|
27
|
+
@interactor_lookup ||= (interactors + organizers).index_by(&:klass)
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def callables_in_file(filename)
|
33
|
+
@callables_in_file ||= {}
|
34
|
+
|
35
|
+
@callables_in_file[filename] ||= _callables_in_file(filename)
|
36
|
+
end
|
37
|
+
|
38
|
+
def _callables_in_file(filename)
|
39
|
+
constant = constant_for(filename)
|
40
|
+
return if constant == Interactify
|
41
|
+
|
42
|
+
internal_klasses = internal_constants_for(constant)
|
43
|
+
|
44
|
+
([constant] + internal_klasses).map do |k|
|
45
|
+
new_callable(filename, k, self)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def internal_constants_for(constant)
|
50
|
+
constant
|
51
|
+
.constants
|
52
|
+
.map { |sym| constant_from_symbol(constant, sym) }
|
53
|
+
.select { |pk| interactor_klass?(pk) }
|
54
|
+
end
|
55
|
+
|
56
|
+
def constant_from_symbol(constant, symbol)
|
57
|
+
constant.module_eval do
|
58
|
+
symbol.to_s.constantize
|
59
|
+
rescue StandardError
|
60
|
+
begin
|
61
|
+
"#{constant.name}::#{symbol}".constantize
|
62
|
+
rescue StandardError
|
63
|
+
nil
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def interactor_klass?(object)
|
69
|
+
return unless object.is_a?(Class) && object.ancestors.include?(Interactor)
|
70
|
+
return if object.is_a?(Sidekiq::Job)
|
71
|
+
|
72
|
+
true
|
73
|
+
end
|
74
|
+
|
75
|
+
def new_callable(filename, klass, wiring)
|
76
|
+
CallableRepresentation.new(filename:, klass:, wiring:)
|
77
|
+
end
|
78
|
+
|
79
|
+
def constant_for(filename)
|
80
|
+
require filename
|
81
|
+
|
82
|
+
underscored_klass_name = underscored_klass_name(filename)
|
83
|
+
underscored_klass_name = trim_rails_design_pattern_folder underscored_klass_name
|
84
|
+
|
85
|
+
klass_name = underscored_klass_name.classify
|
86
|
+
|
87
|
+
should_pluralize = filename[underscored_klass_name.pluralize]
|
88
|
+
klass_name = klass_name.pluralize if should_pluralize
|
89
|
+
|
90
|
+
Object.const_get(klass_name)
|
91
|
+
end
|
92
|
+
|
93
|
+
# Example:
|
94
|
+
# trim_rails_folder("app/interactors/namespace/sub_namespace/class_name.rb")
|
95
|
+
# => "namespace/sub_namespace/class_name.rb"
|
96
|
+
def trim_rails_design_pattern_folder(filename)
|
97
|
+
rails_folders.each do |folder|
|
98
|
+
regexable_folder = Regexp.quote("#{folder}/")
|
99
|
+
regex = /^#{regexable_folder}/
|
100
|
+
|
101
|
+
return filename.gsub(regex, "") if filename.match?(regex)
|
102
|
+
end
|
103
|
+
|
104
|
+
filename
|
105
|
+
end
|
106
|
+
|
107
|
+
def rails_folders = Dir.glob(root / "*").map { Pathname.new _1 }.select(&:directory?).map { |f| File.basename(f) }
|
108
|
+
|
109
|
+
# Example:
|
110
|
+
# "/home/code/something/app/interactors/namespace/sub_namespace/class_name.rb"
|
111
|
+
# "/namespace/sub_namespace/class_name.rb"
|
112
|
+
# ['', 'namespace', 'sub_namespace', 'class_name.rb']
|
113
|
+
# ['namespace', 'sub_namespace', 'class_name.rb']
|
114
|
+
def underscored_klass_name(filename)
|
115
|
+
filename.to_s # "/home/code/something/app/interactors/namespace/sub_namespace/class_name.rb"
|
116
|
+
.gsub(root.to_s, "") # "/namespace/sub_namespace/class_name.rb"
|
117
|
+
.gsub("/concerns", "") # concerns directory is ignored by Zeitwerk
|
118
|
+
.split("/") # "['', 'namespace', 'sub_namespace', 'class_name.rb']
|
119
|
+
.compact_blank # "['namespace', 'sub_namespace', 'class_name.rb']
|
120
|
+
.join("/") # 'namespace/sub_namespace/class_name.rb'
|
121
|
+
.gsub(/\.rb\z/, "") # 'namespace/sub_namespace/class_name'
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Interactify
|
4
|
+
class InteractorWiring
|
5
|
+
class ErrorContext
|
6
|
+
def previously_defined_keys
|
7
|
+
@previously_defined_keys ||= Set.new
|
8
|
+
end
|
9
|
+
|
10
|
+
def append_previously_defined_keys(keys)
|
11
|
+
keys.each do |key|
|
12
|
+
previously_defined_keys << key
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def missing_keys
|
17
|
+
@missing_keys ||= {}
|
18
|
+
end
|
19
|
+
|
20
|
+
def add_promised_keys(promised_keys)
|
21
|
+
promised_keys.each do |key|
|
22
|
+
previously_defined_keys << key
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def infer_missing_keys(callable)
|
27
|
+
new_keys = callable.expected_keys
|
28
|
+
not_in_previous_keys = new_keys.reject { |key| previously_defined_keys.include?(key) }
|
29
|
+
|
30
|
+
add_missing_keys(callable, not_in_previous_keys)
|
31
|
+
end
|
32
|
+
|
33
|
+
def add_missing_keys(callable, not_in_previous_keys)
|
34
|
+
return if not_in_previous_keys.empty?
|
35
|
+
|
36
|
+
missing_keys[callable] ||= Set.new
|
37
|
+
missing_keys[callable] += not_in_previous_keys
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Interactify
|
4
|
+
class InteractorWiring
|
5
|
+
class Files
|
6
|
+
attr_reader :root
|
7
|
+
|
8
|
+
def initialize(root:)
|
9
|
+
@root = root
|
10
|
+
end
|
11
|
+
|
12
|
+
def organizer_files
|
13
|
+
possible_files.select { |_, contents| organizer_file?(contents) }.map(&:first).sort
|
14
|
+
end
|
15
|
+
|
16
|
+
def interactor_files
|
17
|
+
possible_files.select { |_, contents| interactor_file?(contents) }.map(&:first).sort
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def organizer_file?(code)
|
23
|
+
organizer?(code) && (interactified?(code) || vanilla_organizer?(code))
|
24
|
+
end
|
25
|
+
|
26
|
+
def interactor_file?(code)
|
27
|
+
!organizer?(code) && (interactified?(code) || vanilla_interactor?(code))
|
28
|
+
end
|
29
|
+
|
30
|
+
def vanilla_organizer?(code)
|
31
|
+
code[/include Interactor::Organizer/]
|
32
|
+
end
|
33
|
+
|
34
|
+
def vanilla_interactor?(code)
|
35
|
+
code[/include Interactor$/]
|
36
|
+
end
|
37
|
+
|
38
|
+
def interactified?(code)
|
39
|
+
code["include Interactify"]
|
40
|
+
end
|
41
|
+
|
42
|
+
def organizer?(code)
|
43
|
+
code[/^\s+organize/]
|
44
|
+
end
|
45
|
+
|
46
|
+
def possible_files
|
47
|
+
@possible_files ||= Dir.glob("#{root}/**/*.rb").map { |f| [f, File.read(f)] }
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -1,305 +1,90 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support/all"
|
4
|
+
|
5
|
+
require "interactify/interactor_wiring/callable_representation"
|
6
|
+
require "interactify/interactor_wiring/constants"
|
7
|
+
require "interactify/interactor_wiring/files"
|
2
8
|
|
3
9
|
module Interactify
|
4
10
|
class InteractorWiring
|
5
|
-
attr_reader :root, :
|
11
|
+
attr_reader :root, :ignore
|
6
12
|
|
7
|
-
def initialize(root: Rails.root,
|
8
|
-
@root = root.to_s.gsub(%r{/$},
|
9
|
-
@namespace = namespace
|
13
|
+
def initialize(root: Rails.root, ignore: [])
|
14
|
+
@root = root.to_s.gsub(%r{/$}, "")
|
10
15
|
@ignore = ignore
|
11
16
|
end
|
12
17
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
next if ignore_klass?(ignore, organizer.klass)
|
17
|
-
|
18
|
-
errors = organizer.validate_callable
|
19
|
-
all_errors[organizer] = errors
|
20
|
-
end
|
18
|
+
def validate_app
|
19
|
+
errors = organizers.each_with_object({}) do |organizer, all_errors|
|
20
|
+
next if ignore_klass?(ignore, organizer.klass)
|
21
21
|
|
22
|
-
|
22
|
+
errors = organizer.validate_callable
|
23
|
+
all_errors[organizer] = errors
|
23
24
|
end
|
24
25
|
|
25
|
-
|
26
|
-
formatted_errors = []
|
27
|
-
|
28
|
-
all_errors.each do |organizer, error_context|
|
29
|
-
next if ignore_klass?(ignore, organizer.klass)
|
30
|
-
|
31
|
-
error_context.missing_keys.each do |interactor, missing|
|
32
|
-
next if ignore_klass?(ignore, interactor.klass)
|
33
|
-
|
34
|
-
formatted_errors << <<~ERROR
|
35
|
-
Missing keys: #{missing.to_a.map(&:to_sym).map(&:inspect).join(', ')}
|
36
|
-
in: #{interactor.klass}
|
37
|
-
for: #{organizer.klass}
|
38
|
-
ERROR
|
39
|
-
end
|
40
|
-
end
|
41
|
-
|
42
|
-
formatted_errors.join("\n\n")
|
43
|
-
end
|
44
|
-
|
45
|
-
def ignore_klass?(ignore, klass)
|
46
|
-
case ignore
|
47
|
-
when Array
|
48
|
-
ignore.any? { ignore_klass?(_1, klass) }
|
49
|
-
when Regexp
|
50
|
-
klass.to_s =~ ignore
|
51
|
-
when String
|
52
|
-
klass.to_s[ignore]
|
53
|
-
when Proc
|
54
|
-
ignore.call(klass)
|
55
|
-
when Class
|
56
|
-
klass <= ignore
|
57
|
-
end
|
58
|
-
end
|
26
|
+
format_errors(errors)
|
59
27
|
end
|
60
28
|
|
61
|
-
|
62
|
-
|
29
|
+
def format_errors(all_errors)
|
30
|
+
formatted_errors = []
|
63
31
|
|
64
|
-
|
65
|
-
|
66
|
-
def initialize(filename:, klass:, wiring:, organizer: nil)
|
67
|
-
@filename = filename
|
68
|
-
@klass = klass
|
69
|
-
@wiring = wiring
|
70
|
-
end
|
71
|
-
|
72
|
-
def validate_callable(error_context: ErrorContext.new)
|
73
|
-
if organizer?
|
74
|
-
assign_previously_defined(error_context: error_context)
|
75
|
-
validate_children(error_context: error_context)
|
76
|
-
end
|
77
|
-
|
78
|
-
validate_self(error_context: error_context)
|
79
|
-
end
|
80
|
-
|
81
|
-
def promised_keys
|
82
|
-
Array(klass.contract.promises.instance_eval { @terms }.json&.rules&.keys)
|
83
|
-
end
|
84
|
-
|
85
|
-
def expected_keys
|
86
|
-
Array(klass.contract.expectations.instance_eval { @terms }.json&.rules&.keys)
|
32
|
+
each_error(all_errors) do |missing, interactor, organizer|
|
33
|
+
format_error(missing, interactor, organizer, formatted_errors)
|
87
34
|
end
|
88
35
|
|
89
|
-
|
90
|
-
expected_keys.concat(promised_keys)
|
91
|
-
end
|
92
|
-
|
93
|
-
def inspect
|
94
|
-
"#<#{self.class.name}#{object_id} @filename=#{filename}, @klass=#{klass.name}>"
|
95
|
-
end
|
96
|
-
|
97
|
-
def organizer?
|
98
|
-
klass.respond_to?(:organized)
|
99
|
-
end
|
100
|
-
|
101
|
-
def assign_previously_defined(error_context:)
|
102
|
-
return unless contract?
|
103
|
-
|
104
|
-
error_context.append_previously_defined_keys(all_keys)
|
105
|
-
end
|
106
|
-
|
107
|
-
def validate_children(error_context:)
|
108
|
-
klass.organized.each do |interactor|
|
109
|
-
nested_callable = interactor_lookup[interactor]
|
110
|
-
next if nested_callable.nil?
|
111
|
-
|
112
|
-
error_context = nested_callable.validate_callable(error_context: error_context)
|
113
|
-
end
|
114
|
-
|
115
|
-
error_context
|
116
|
-
end
|
117
|
-
|
118
|
-
private
|
119
|
-
|
120
|
-
def contract?
|
121
|
-
klass.ancestors.include? Interactor::Contracts
|
122
|
-
end
|
123
|
-
|
124
|
-
def validate_self(error_context:)
|
125
|
-
return error_context unless contract?
|
126
|
-
|
127
|
-
error_context.infer_missing_keys(self)
|
128
|
-
error_context.add_promised_keys(promised_keys)
|
129
|
-
error_context
|
130
|
-
end
|
36
|
+
formatted_errors.join("\n\n")
|
131
37
|
end
|
132
38
|
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
end
|
137
|
-
|
138
|
-
def append_previously_defined_keys(keys)
|
139
|
-
keys.each do |key|
|
140
|
-
previously_defined_keys << key
|
141
|
-
end
|
142
|
-
end
|
39
|
+
def each_error(all_errors)
|
40
|
+
all_errors.each do |organizer, error_context|
|
41
|
+
next if ignore_klass?(ignore, organizer.klass)
|
143
42
|
|
144
|
-
|
145
|
-
|
146
|
-
end
|
43
|
+
error_context.missing_keys.each do |interactor, missing|
|
44
|
+
next if ignore_klass?(ignore, interactor.klass)
|
147
45
|
|
148
|
-
|
149
|
-
promised_keys.each do |key|
|
150
|
-
previously_defined_keys << key
|
46
|
+
yield missing, interactor, organizer
|
151
47
|
end
|
152
48
|
end
|
153
|
-
|
154
|
-
def infer_missing_keys(callable)
|
155
|
-
new_keys = callable.expected_keys
|
156
|
-
not_in_previous_keys = new_keys.reject { |key| previously_defined_keys.include?(key) }
|
157
|
-
|
158
|
-
add_missing_keys(callable, not_in_previous_keys)
|
159
|
-
end
|
160
|
-
|
161
|
-
def add_missing_keys(callable, not_in_previous_keys)
|
162
|
-
return if not_in_previous_keys.empty?
|
163
|
-
|
164
|
-
missing_keys[callable] ||= Set.new
|
165
|
-
missing_keys[callable] += not_in_previous_keys
|
166
|
-
end
|
167
49
|
end
|
168
50
|
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
end.compact.select(&:organizer?)
|
176
|
-
end
|
177
|
-
|
178
|
-
def interactors
|
179
|
-
@interactors ||= interactor_files.flat_map do |f|
|
180
|
-
callables_in_file(f)
|
181
|
-
end.compact.reject(&:organizer?)
|
182
|
-
end
|
183
|
-
|
184
|
-
def callables_in_file(f)
|
185
|
-
@callables_in_file ||= {}
|
186
|
-
|
187
|
-
@callables_in_file[f] ||= _callables_in_file(f)
|
188
|
-
end
|
189
|
-
|
190
|
-
def _callables_in_file(f)
|
191
|
-
constant = constant_for(f)
|
192
|
-
return if constant == Interactify
|
193
|
-
|
194
|
-
internal_klasses = internal_constants_for(constant)
|
195
|
-
|
196
|
-
([constant] + internal_klasses).map { |k| new_callable(f, k, self) }
|
197
|
-
end
|
198
|
-
|
199
|
-
def internal_constants_for(constant)
|
200
|
-
constant
|
201
|
-
.constants
|
202
|
-
.map { |sym| constant_from_symbol(constant, sym) }
|
203
|
-
.select { |pk| interactor_klass?(pk) }
|
204
|
-
end
|
205
|
-
|
206
|
-
def constant_from_symbol(constant, symbol)
|
207
|
-
constant.module_eval do
|
208
|
-
symbol.to_s.constantize
|
209
|
-
rescue StandardError
|
210
|
-
"#{constant.name}::#{symbol}".constantize rescue nil
|
211
|
-
end
|
212
|
-
end
|
213
|
-
|
214
|
-
def interactor_klass?(object)
|
215
|
-
return unless object.is_a? Class
|
216
|
-
|
217
|
-
object.ancestors.include?(Interactor) || object.ancestors.include?(Interactor::Organizer)
|
218
|
-
end
|
219
|
-
|
220
|
-
def new_callable(filename, klass, wiring)
|
221
|
-
CallableRepresentation.new(filename: filename, klass: klass, wiring: wiring)
|
222
|
-
end
|
223
|
-
|
224
|
-
def interactor_lookup
|
225
|
-
@interactor_lookup ||= (interactors + organizers).index_by(&:klass)
|
226
|
-
end
|
227
|
-
|
228
|
-
private
|
229
|
-
|
230
|
-
def constant_for(filename)
|
231
|
-
require filename
|
232
|
-
|
233
|
-
underscored_klass_name = underscored_klass_name_without_outer_namespace(filename)
|
234
|
-
underscored_klass_name = trim_rails_folder underscored_klass_name
|
235
|
-
|
236
|
-
klass_name = underscored_klass_name.classify
|
237
|
-
|
238
|
-
should_pluralize = filename[underscored_klass_name.pluralize]
|
239
|
-
klass_name = klass_name.pluralize if should_pluralize
|
240
|
-
|
241
|
-
outer_namespace_constant.const_get(klass_name)
|
242
|
-
end
|
243
|
-
|
244
|
-
# Example:
|
245
|
-
# trim_rails_folder("interactors/namespace/sub_namespace/class_name.rb")
|
246
|
-
# => "namespace/sub_namespace/class_name.rb"
|
247
|
-
def trim_rails_folder(filename)
|
248
|
-
rails_folders = Dir.glob(Interactify.root / '*').map { |f| File.basename(f) }
|
249
|
-
|
250
|
-
rails_folders.each do |folder|
|
251
|
-
regexable_folder = Regexp.quote("#{folder}/")
|
252
|
-
regex = /^#{regexable_folder}/
|
253
|
-
|
254
|
-
return filename.gsub(regex, '') if filename.match?(regex)
|
255
|
-
end
|
256
|
-
|
257
|
-
filename
|
258
|
-
end
|
259
|
-
|
260
|
-
# Example:
|
261
|
-
# "/home/code/something/app/interactors/namespace/sub_namespace/class_name.rb"
|
262
|
-
# "/namespace/sub_namespace/class_name.rb"
|
263
|
-
# ['', 'namespace', 'sub_namespace', 'class_name.rb']
|
264
|
-
# ['namespace', 'sub_namespace', 'class_name.rb']
|
265
|
-
# remove outernamespace (SpecSupport)
|
266
|
-
def underscored_klass_name_without_outer_namespace(filename)
|
267
|
-
filename.to_s # "/home/code/something/app/interactors/namespace/sub_namespace/class_name.rb"
|
268
|
-
.gsub(root.to_s, '') # "/namespace/sub_namespace/class_name.rb"
|
269
|
-
.gsub('/concerns', '') # concerns directory is ignored by Zeitwerk
|
270
|
-
.split('/') # "['', 'namespace', 'sub_namespace', 'class_name.rb']
|
271
|
-
.compact_blank # "['namespace', 'sub_namespace', 'class_name.rb']
|
272
|
-
.reject.with_index { |segment, i| i.zero? && segment == namespace }
|
273
|
-
.join('/') # 'namespace/sub_namespace/class_name.rb'
|
274
|
-
.gsub(/\.rb\z/, '') # 'namespace/sub_namespace/class_name'
|
275
|
-
end
|
276
|
-
|
277
|
-
def outer_namespace_constant
|
278
|
-
@outer_namespace_constant ||= Object.const_get(namespace)
|
279
|
-
end
|
51
|
+
def format_error(missing, interactor, organizer, formatted_errors)
|
52
|
+
formatted_errors << <<~ERROR
|
53
|
+
Missing keys: #{missing.to_a.map(&:to_sym).map(&:inspect).join(', ')}
|
54
|
+
expected in: #{interactor.klass}
|
55
|
+
called by: #{organizer.klass}
|
56
|
+
ERROR
|
280
57
|
end
|
281
58
|
|
282
|
-
|
283
|
-
|
284
|
-
|
59
|
+
def ignore_klass?(ignore, klass)
|
60
|
+
case ignore
|
61
|
+
when Array
|
62
|
+
ignore.any? { ignore_klass?(_1, klass) }
|
63
|
+
when Regexp
|
64
|
+
klass.to_s =~ ignore
|
65
|
+
when String
|
66
|
+
klass.to_s[ignore]
|
67
|
+
when Proc
|
68
|
+
ignore.call(klass)
|
69
|
+
when Class
|
70
|
+
klass <= ignore
|
285
71
|
end
|
72
|
+
end
|
286
73
|
|
287
|
-
|
288
|
-
possible_files.select { |_, contents| interactor_file?(contents) }.map(&:first).sort
|
289
|
-
end
|
74
|
+
delegate :organizers, :interactors, :interactor_lookup, to: :constants
|
290
75
|
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
76
|
+
def constants
|
77
|
+
@constants ||= Constants.new(
|
78
|
+
root:,
|
79
|
+
organizer_files:,
|
80
|
+
interactor_files:
|
81
|
+
)
|
82
|
+
end
|
295
83
|
|
296
|
-
|
297
|
-
file_contents['include Interactify'] || file_contents[/include Interactor$/]
|
298
|
-
end
|
84
|
+
delegate :organizer_files, :interactor_files, to: :files
|
299
85
|
|
300
|
-
|
301
|
-
|
302
|
-
end
|
86
|
+
def files
|
87
|
+
@files ||= Files.new(root:)
|
303
88
|
end
|
304
89
|
end
|
305
90
|
end
|