hanami-view 1.3.3 → 2.0.0.alpha2
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/CHANGELOG.md +3 -10
- data/LICENSE +20 -0
- data/README.md +20 -862
- data/hanami-view.gemspec +26 -16
- data/lib/hanami-view.rb +3 -1
- data/lib/hanami/view.rb +208 -223
- data/lib/hanami/view/application_configuration.rb +77 -0
- data/lib/hanami/view/application_context.rb +35 -0
- data/lib/hanami/view/application_view.rb +89 -0
- data/lib/hanami/view/context.rb +97 -0
- data/lib/hanami/view/context_helpers/content_helpers.rb +26 -0
- data/lib/hanami/view/decorated_attributes.rb +82 -0
- data/lib/hanami/view/errors.rb +19 -56
- data/lib/hanami/view/exposure.rb +126 -0
- data/lib/hanami/view/exposures.rb +74 -0
- data/lib/hanami/view/part.rb +217 -0
- data/lib/hanami/view/part_builder.rb +140 -0
- data/lib/hanami/view/path.rb +68 -0
- data/lib/hanami/view/render_environment.rb +62 -0
- data/lib/hanami/view/render_environment_missing.rb +44 -0
- data/lib/hanami/view/rendered.rb +55 -0
- data/lib/hanami/view/renderer.rb +79 -0
- data/lib/hanami/view/scope.rb +189 -0
- data/lib/hanami/view/scope_builder.rb +98 -0
- data/lib/hanami/view/standalone_view.rb +396 -0
- data/lib/hanami/view/tilt.rb +78 -0
- data/lib/hanami/view/tilt/erb.rb +26 -0
- data/lib/hanami/view/tilt/erbse.rb +21 -0
- data/lib/hanami/view/tilt/haml.rb +26 -0
- data/lib/hanami/view/version.rb +5 -5
- metadata +114 -70
- data/LICENSE.md +0 -22
- data/lib/hanami/layout.rb +0 -190
- data/lib/hanami/presenter.rb +0 -98
- data/lib/hanami/view/configuration.rb +0 -504
- data/lib/hanami/view/dsl.rb +0 -347
- data/lib/hanami/view/escape.rb +0 -225
- data/lib/hanami/view/inheritable.rb +0 -54
- data/lib/hanami/view/rendering.rb +0 -294
- data/lib/hanami/view/rendering/layout_finder.rb +0 -128
- data/lib/hanami/view/rendering/layout_registry.rb +0 -69
- data/lib/hanami/view/rendering/layout_scope.rb +0 -281
- data/lib/hanami/view/rendering/null_layout.rb +0 -52
- data/lib/hanami/view/rendering/null_local.rb +0 -82
- data/lib/hanami/view/rendering/null_template.rb +0 -83
- data/lib/hanami/view/rendering/null_view.rb +0 -26
- data/lib/hanami/view/rendering/options.rb +0 -24
- data/lib/hanami/view/rendering/partial.rb +0 -31
- data/lib/hanami/view/rendering/partial_file.rb +0 -29
- data/lib/hanami/view/rendering/partial_finder.rb +0 -75
- data/lib/hanami/view/rendering/partial_templates_finder.rb +0 -73
- data/lib/hanami/view/rendering/registry.rb +0 -134
- data/lib/hanami/view/rendering/scope.rb +0 -108
- data/lib/hanami/view/rendering/subscope.rb +0 -56
- data/lib/hanami/view/rendering/template.rb +0 -69
- data/lib/hanami/view/rendering/template_finder.rb +0 -55
- data/lib/hanami/view/rendering/template_name.rb +0 -50
- data/lib/hanami/view/rendering/templates_finder.rb +0 -144
- data/lib/hanami/view/rendering/view_finder.rb +0 -37
- data/lib/hanami/view/template.rb +0 -57
@@ -0,0 +1,126 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "dry/core/equalizer"
|
4
|
+
|
5
|
+
module Hanami
|
6
|
+
class View
|
7
|
+
# An exposure defined on a view
|
8
|
+
#
|
9
|
+
# @api private
|
10
|
+
class Exposure
|
11
|
+
include Dry::Equalizer(:name, :proc, :object, :options)
|
12
|
+
|
13
|
+
EXPOSURE_DEPENDENCY_PARAMETER_TYPES = %i[req opt].freeze
|
14
|
+
INPUT_PARAMETER_TYPES = %i[key keyreq keyrest].freeze
|
15
|
+
|
16
|
+
attr_reader :name
|
17
|
+
attr_reader :proc
|
18
|
+
attr_reader :object
|
19
|
+
attr_reader :options
|
20
|
+
|
21
|
+
def initialize(name, proc = nil, object = nil, **options)
|
22
|
+
@name = name
|
23
|
+
@proc = prepare_proc(proc, object)
|
24
|
+
@object = object
|
25
|
+
@options = options
|
26
|
+
end
|
27
|
+
|
28
|
+
def bind(obj)
|
29
|
+
self.class.new(name, proc, obj, **options)
|
30
|
+
end
|
31
|
+
|
32
|
+
def dependency_names
|
33
|
+
if proc
|
34
|
+
proc.parameters.each_with_object([]) { |(type, name), names|
|
35
|
+
names << name if EXPOSURE_DEPENDENCY_PARAMETER_TYPES.include?(type)
|
36
|
+
}
|
37
|
+
else
|
38
|
+
[]
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def input_keys
|
43
|
+
if proc
|
44
|
+
proc.parameters.each_with_object([]) { |(type, name), keys|
|
45
|
+
keys << name if INPUT_PARAMETER_TYPES.include?(type)
|
46
|
+
}
|
47
|
+
else
|
48
|
+
[]
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def for_layout?
|
53
|
+
options.fetch(:layout) { false }
|
54
|
+
end
|
55
|
+
|
56
|
+
def decorate?
|
57
|
+
options.fetch(:decorate) { true }
|
58
|
+
end
|
59
|
+
|
60
|
+
def private?
|
61
|
+
options.fetch(:private) { false }
|
62
|
+
end
|
63
|
+
|
64
|
+
def default_value
|
65
|
+
options[:default]
|
66
|
+
end
|
67
|
+
|
68
|
+
def call(input, locals = {})
|
69
|
+
if proc
|
70
|
+
call_proc(input, locals)
|
71
|
+
else
|
72
|
+
input.fetch(name) { default_value }
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
private
|
77
|
+
|
78
|
+
def call_proc(input, locals)
|
79
|
+
args, keywords = proc_args(input, locals)
|
80
|
+
|
81
|
+
if keywords.empty?
|
82
|
+
if proc.is_a?(Method)
|
83
|
+
proc.(*args)
|
84
|
+
else
|
85
|
+
object.instance_exec(*args, &proc)
|
86
|
+
end
|
87
|
+
else
|
88
|
+
if proc.is_a?(Method)
|
89
|
+
proc.(*args, **keywords)
|
90
|
+
else
|
91
|
+
object.instance_exec(*args, **keywords, &proc)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def proc_args(input, locals)
|
97
|
+
dependency_args = proc_dependency_args(locals)
|
98
|
+
keywords = proc_input_args(input)
|
99
|
+
|
100
|
+
if keywords.empty?
|
101
|
+
[dependency_args, {}]
|
102
|
+
else
|
103
|
+
[dependency_args, keywords]
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def proc_dependency_args(locals)
|
108
|
+
dependency_names.map { |name| locals.fetch(name) }
|
109
|
+
end
|
110
|
+
|
111
|
+
def proc_input_args(input)
|
112
|
+
input_keys.each_with_object({}) { |key, args|
|
113
|
+
args[key] = input[key] if input.key?(key)
|
114
|
+
}
|
115
|
+
end
|
116
|
+
|
117
|
+
def prepare_proc(proc, object)
|
118
|
+
if proc
|
119
|
+
proc
|
120
|
+
elsif object.respond_to?(name, _include_private = true)
|
121
|
+
object.method(name)
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "tsort"
|
4
|
+
require "dry/core/equalizer"
|
5
|
+
require_relative "exposure"
|
6
|
+
|
7
|
+
module Hanami
|
8
|
+
class View
|
9
|
+
# @api private
|
10
|
+
class Exposures
|
11
|
+
include Dry::Equalizer(:exposures)
|
12
|
+
include TSort
|
13
|
+
|
14
|
+
attr_reader :exposures
|
15
|
+
|
16
|
+
def initialize(exposures = {})
|
17
|
+
@exposures = exposures
|
18
|
+
end
|
19
|
+
|
20
|
+
def key?(name)
|
21
|
+
exposures.key?(name)
|
22
|
+
end
|
23
|
+
|
24
|
+
def [](name)
|
25
|
+
exposures[name]
|
26
|
+
end
|
27
|
+
|
28
|
+
def each(&block)
|
29
|
+
exposures.each(&block)
|
30
|
+
end
|
31
|
+
|
32
|
+
def add(name, proc = nil, **options)
|
33
|
+
exposures[name] = Exposure.new(name, proc, **options)
|
34
|
+
end
|
35
|
+
|
36
|
+
def import(name, exposure)
|
37
|
+
exposures[name] = exposure.dup
|
38
|
+
end
|
39
|
+
|
40
|
+
def bind(obj)
|
41
|
+
bound_exposures = exposures.each_with_object({}) { |(name, exposure), memo|
|
42
|
+
memo[name] = exposure.bind(obj)
|
43
|
+
}
|
44
|
+
|
45
|
+
self.class.new(bound_exposures)
|
46
|
+
end
|
47
|
+
|
48
|
+
def call(input)
|
49
|
+
# rubocop:disable Style/MultilineBlockChain
|
50
|
+
tsort.each_with_object({}) { |name, memo|
|
51
|
+
next unless (exposure = self[name])
|
52
|
+
|
53
|
+
value = exposure.(input, memo)
|
54
|
+
value = yield(value, exposure) if block_given?
|
55
|
+
|
56
|
+
memo[name] = value
|
57
|
+
}.each_with_object({}) { |(name, value), memo|
|
58
|
+
memo[name] = value unless self[name].private?
|
59
|
+
}
|
60
|
+
# rubocop:enable Style/MultilineBlockChain
|
61
|
+
end
|
62
|
+
|
63
|
+
private
|
64
|
+
|
65
|
+
def tsort_each_node(&block)
|
66
|
+
exposures.each_key(&block)
|
67
|
+
end
|
68
|
+
|
69
|
+
def tsort_each_child(name, &block)
|
70
|
+
self[name].dependency_names.each(&block) if exposures.key?(name)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,217 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "dry/core/equalizer"
|
4
|
+
require_relative "decorated_attributes"
|
5
|
+
require_relative "render_environment_missing"
|
6
|
+
|
7
|
+
module Hanami
|
8
|
+
class View
|
9
|
+
# Decorates an exposure value and provides a place to encapsulate
|
10
|
+
# view-specific behavior alongside your application's domain objects.
|
11
|
+
#
|
12
|
+
# @abstract Subclass this and provide your own methods adding view-specific
|
13
|
+
# behavior. You should not override `#initialize`.
|
14
|
+
#
|
15
|
+
# @see https://dry-rb.org/gems/dry-view/parts/
|
16
|
+
#
|
17
|
+
# @api public
|
18
|
+
class Part
|
19
|
+
# @api private
|
20
|
+
CONVENIENCE_METHODS = %i[
|
21
|
+
format
|
22
|
+
context
|
23
|
+
render
|
24
|
+
scope
|
25
|
+
value
|
26
|
+
].freeze
|
27
|
+
|
28
|
+
include Dry::Equalizer(:_name, :_value, :_render_env)
|
29
|
+
include DecoratedAttributes
|
30
|
+
|
31
|
+
# The part's name. This comes from the exposure supplying the value.
|
32
|
+
#
|
33
|
+
# @return [Symbol] name
|
34
|
+
#
|
35
|
+
# @api public
|
36
|
+
attr_reader :_name
|
37
|
+
|
38
|
+
# The decorated value. This is the value returned from the exposure.
|
39
|
+
#
|
40
|
+
# @overload _value
|
41
|
+
# Returns the value.
|
42
|
+
# @overload value
|
43
|
+
# A convenience alias for `_value`. Is available unless the value itself
|
44
|
+
# responds to `#value`.
|
45
|
+
#
|
46
|
+
# @return [Object] value
|
47
|
+
#
|
48
|
+
# @api public
|
49
|
+
attr_reader :_value
|
50
|
+
|
51
|
+
# The current render environment
|
52
|
+
#
|
53
|
+
# @return [RenderEnvironment] render environment
|
54
|
+
#
|
55
|
+
# @api private
|
56
|
+
attr_reader :_render_env
|
57
|
+
|
58
|
+
# Determins a part name (when initialized without one). Intended for use
|
59
|
+
# only while unit testing Parts.
|
60
|
+
#
|
61
|
+
# @api private
|
62
|
+
def self.part_name(inflector)
|
63
|
+
name ? inflector.underscore(inflector.demodulize(name)) : "part"
|
64
|
+
end
|
65
|
+
|
66
|
+
# Returns a new Part instance
|
67
|
+
#
|
68
|
+
# @param name [Symbol] part name
|
69
|
+
# @param value [Object] the value to decorate
|
70
|
+
# @param render_env [RenderEnvironment] render environment
|
71
|
+
#
|
72
|
+
# @api public
|
73
|
+
def initialize(
|
74
|
+
render_env: RenderEnvironmentMissing.new,
|
75
|
+
name: self.class.part_name(render_env.inflector),
|
76
|
+
value:
|
77
|
+
)
|
78
|
+
@_name = name
|
79
|
+
@_value = value
|
80
|
+
@_render_env = render_env
|
81
|
+
end
|
82
|
+
|
83
|
+
# The template format for the current render environment.
|
84
|
+
#
|
85
|
+
# @overload _format
|
86
|
+
# Returns the format.
|
87
|
+
# @overload format
|
88
|
+
# A convenience alias for `#_format.` Is available unless the value
|
89
|
+
# itself responds to `#format`.
|
90
|
+
#
|
91
|
+
# @return [Symbol] format
|
92
|
+
#
|
93
|
+
# @api public
|
94
|
+
def _format
|
95
|
+
_render_env.format
|
96
|
+
end
|
97
|
+
|
98
|
+
# The context object for the current render environment
|
99
|
+
#
|
100
|
+
# @overload _context
|
101
|
+
# Returns the context.
|
102
|
+
# @overload context
|
103
|
+
# A convenience alias for `#_context`. Is available unless the value
|
104
|
+
# itself responds to `#context`.
|
105
|
+
#
|
106
|
+
# @return [Context] context
|
107
|
+
#
|
108
|
+
# @api public
|
109
|
+
def _context
|
110
|
+
_render_env.context
|
111
|
+
end
|
112
|
+
|
113
|
+
# Renders a new partial with the part included in its locals.
|
114
|
+
#
|
115
|
+
# @overload _render(partial_name, as: _name, **locals, &block)
|
116
|
+
# Renders the partial.
|
117
|
+
# @overload render(partial_name, as: _name, **locals, &block)
|
118
|
+
# A convenience alias for `#_render`. Is available unless the value
|
119
|
+
# itself responds to `#render`.
|
120
|
+
#
|
121
|
+
# @param partial_name [Symbol, String] partial name
|
122
|
+
# @param as [Symbol] the name for the Part to assume in the partial's locals. Defaults to
|
123
|
+
# the Part's `_name`.
|
124
|
+
# @param locals [Hash<Symbol, Object>] other locals to provide the partial
|
125
|
+
#
|
126
|
+
# @return [String] rendered partial
|
127
|
+
#
|
128
|
+
# @api public
|
129
|
+
# rubocop:disable Naming/UncommunicativeMethodParamName
|
130
|
+
def _render(partial_name, as: _name, **locals, &block)
|
131
|
+
_render_env.partial(partial_name, _render_env.scope({as => self}.merge(locals)), &block)
|
132
|
+
end
|
133
|
+
# rubocop:enable Naming/UncommunicativeMethodParamName
|
134
|
+
|
135
|
+
# Builds a new scope with the part included in its locals.
|
136
|
+
#
|
137
|
+
# @overload _scope(scope_name = nil, **locals)
|
138
|
+
# Builds the scope.
|
139
|
+
# @overload scope(scope_name = nil, **locals)
|
140
|
+
# A convenience alias for `#_scope`. Is available unless the value
|
141
|
+
# itself responds to `#scope`.
|
142
|
+
#
|
143
|
+
# @param scope_name [Symbol, nil] scope name, used by the scope builder to determine the
|
144
|
+
# scope class
|
145
|
+
# @param locals [Hash<Symbol, Object>] other locals to provide the partial
|
146
|
+
#
|
147
|
+
# @return [Hanami::View::Scope] scope
|
148
|
+
#
|
149
|
+
# @api public
|
150
|
+
def _scope(scope_name = nil, **locals)
|
151
|
+
_render_env.scope(scope_name, {_name => self}.merge(locals))
|
152
|
+
end
|
153
|
+
|
154
|
+
# Returns a string representation of the value
|
155
|
+
#
|
156
|
+
# @return [String]
|
157
|
+
#
|
158
|
+
# @api public
|
159
|
+
def to_s
|
160
|
+
_value.to_s
|
161
|
+
end
|
162
|
+
|
163
|
+
# Builds a new a part with the given parameters
|
164
|
+
#
|
165
|
+
# This is helpful for manually constructing a new part object that
|
166
|
+
# maintains the current render environment.
|
167
|
+
#
|
168
|
+
# However, using `.decorate` is preferred for declaring attributes that
|
169
|
+
# should also be decorated as parts.
|
170
|
+
#
|
171
|
+
# @see DecoratedAttributes::ClassInterface#decorate
|
172
|
+
#
|
173
|
+
# @param klass [Class] part class to use (defaults to the part's class)
|
174
|
+
# @param name [Symbol] part name (defaults to the part's name)
|
175
|
+
# @param value [Object] value to decorate (defaults to the part's value)
|
176
|
+
# @param options[Hash<Symbol, Object>] other options to provide when initializing the new part
|
177
|
+
#
|
178
|
+
# @api public
|
179
|
+
def new(klass = self.class, name: _name, value: _value, **options)
|
180
|
+
klass.new(
|
181
|
+
name: name,
|
182
|
+
value: value,
|
183
|
+
render_env: _render_env,
|
184
|
+
**options
|
185
|
+
)
|
186
|
+
end
|
187
|
+
|
188
|
+
# Returns a string representation of the part
|
189
|
+
#
|
190
|
+
# @return [String]
|
191
|
+
#
|
192
|
+
# @api public
|
193
|
+
def inspect
|
194
|
+
%(#<#{self.class.name} name=#{_name.inspect} value=#{_value.inspect}>)
|
195
|
+
end
|
196
|
+
|
197
|
+
private
|
198
|
+
|
199
|
+
# Handles missing methods. If the `_value` responds to the method, then
|
200
|
+
# the method will be sent to the value.
|
201
|
+
def method_missing(name, *args, &block)
|
202
|
+
if _value.respond_to?(name)
|
203
|
+
_value.public_send(name, *args, &block)
|
204
|
+
elsif CONVENIENCE_METHODS.include?(name)
|
205
|
+
__send__(:"_#{name}", *args, &block)
|
206
|
+
else
|
207
|
+
super
|
208
|
+
end
|
209
|
+
end
|
210
|
+
ruby2_keywords(:method_missing) if respond_to?(:ruby2_keywords, true)
|
211
|
+
|
212
|
+
def respond_to_missing?(name, include_private = false)
|
213
|
+
CONVENIENCE_METHODS.include?(name) || _value.respond_to?(name, include_private) || super
|
214
|
+
end
|
215
|
+
end
|
216
|
+
end
|
217
|
+
end
|
@@ -0,0 +1,140 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "dry/core/cache"
|
4
|
+
require "dry/core/equalizer"
|
5
|
+
require_relative "part"
|
6
|
+
|
7
|
+
module Hanami
|
8
|
+
class View
|
9
|
+
# Decorates exposure values with matching parts
|
10
|
+
#
|
11
|
+
# @api private
|
12
|
+
class PartBuilder
|
13
|
+
extend Dry::Core::Cache
|
14
|
+
include Dry::Equalizer(:namespace)
|
15
|
+
|
16
|
+
attr_reader :namespace
|
17
|
+
attr_reader :render_env
|
18
|
+
|
19
|
+
# Returns a new instance of PartBuilder
|
20
|
+
#
|
21
|
+
# @api private
|
22
|
+
def initialize(namespace: nil, render_env: nil)
|
23
|
+
@namespace = namespace
|
24
|
+
@render_env = render_env
|
25
|
+
end
|
26
|
+
|
27
|
+
# @api private
|
28
|
+
def for_render_env(render_env)
|
29
|
+
return self if render_env == self.render_env
|
30
|
+
|
31
|
+
self.class.new(namespace: namespace, render_env: render_env)
|
32
|
+
end
|
33
|
+
|
34
|
+
# Decorates an exposure value
|
35
|
+
#
|
36
|
+
# @param name [Symbol] exposure name
|
37
|
+
# @param value [Object] exposure value
|
38
|
+
# @param options [Hash] exposure options
|
39
|
+
#
|
40
|
+
# @return [Hanami::View::Part] decorated value
|
41
|
+
#
|
42
|
+
# @api private
|
43
|
+
def call(name, value, **options)
|
44
|
+
builder = value.respond_to?(:to_ary) ? :build_collection_part : :build_part
|
45
|
+
|
46
|
+
send(builder, name, value, **options)
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
def build_part(name, value, **options)
|
52
|
+
klass = part_class(name: name, **options)
|
53
|
+
|
54
|
+
klass.new(
|
55
|
+
name: name,
|
56
|
+
value: value,
|
57
|
+
render_env: render_env
|
58
|
+
)
|
59
|
+
end
|
60
|
+
|
61
|
+
def build_collection_part(name, value, **options)
|
62
|
+
collection_as = collection_options(name: name, **options)[:as]
|
63
|
+
item_name, item_as = collection_item_options(name: name, **options).values_at(:name, :as)
|
64
|
+
|
65
|
+
arr = value.to_ary.map { |obj|
|
66
|
+
build_part(item_name, obj, **options.merge(as: item_as))
|
67
|
+
}
|
68
|
+
|
69
|
+
build_part(name, arr, **options.merge(as: collection_as))
|
70
|
+
end
|
71
|
+
|
72
|
+
# rubocop:disable Lint/UnusedMethodArgument
|
73
|
+
def collection_options(name:, **options)
|
74
|
+
collection_as = options[:as].is_a?(Array) ? options[:as].first : nil
|
75
|
+
|
76
|
+
options.merge(as: collection_as)
|
77
|
+
end
|
78
|
+
# rubocop:enable Lint/UnusedMethodArgument
|
79
|
+
|
80
|
+
def collection_item_options(name:, **options)
|
81
|
+
singular_name = inflector.singularize(name).to_sym
|
82
|
+
singular_as =
|
83
|
+
if options[:as].is_a?(Array)
|
84
|
+
options[:as].last if options[:as].length > 1
|
85
|
+
else
|
86
|
+
options[:as]
|
87
|
+
end
|
88
|
+
|
89
|
+
if singular_as && !singular_as.is_a?(Class)
|
90
|
+
singular_as = inflector.singularize(singular_as.to_s)
|
91
|
+
end
|
92
|
+
|
93
|
+
options.merge(
|
94
|
+
name: singular_name,
|
95
|
+
as: singular_as
|
96
|
+
)
|
97
|
+
end
|
98
|
+
|
99
|
+
def part_class(name:, fallback_class: Part, **options)
|
100
|
+
name = options[:as] || name
|
101
|
+
|
102
|
+
if name.is_a?(Class)
|
103
|
+
name
|
104
|
+
else
|
105
|
+
fetch_or_store(namespace, name, fallback_class) do
|
106
|
+
resolve_part_class(name: name, fallback_class: fallback_class)
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
# rubocop:disable Metrics/PerceivedComplexity
|
112
|
+
def resolve_part_class(name:, fallback_class:)
|
113
|
+
return fallback_class unless namespace
|
114
|
+
|
115
|
+
name = inflector.camelize(name.to_s)
|
116
|
+
|
117
|
+
# Give autoloaders a chance to act
|
118
|
+
begin
|
119
|
+
klass = namespace.const_get(name)
|
120
|
+
rescue NameError # rubocop:disable Lint/HandleExceptions
|
121
|
+
end
|
122
|
+
|
123
|
+
if !klass && namespace.const_defined?(name, false)
|
124
|
+
klass = namespace.const_get(name)
|
125
|
+
end
|
126
|
+
|
127
|
+
if klass && klass < Part
|
128
|
+
klass
|
129
|
+
else
|
130
|
+
fallback_class
|
131
|
+
end
|
132
|
+
end
|
133
|
+
# rubocop:enable Metrics/PerceivedComplexity
|
134
|
+
|
135
|
+
def inflector
|
136
|
+
render_env.inflector
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|