hanami-view 2.0.0.alpha8 → 2.1.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +30 -0
  3. data/README.md +15 -3
  4. data/hanami-view.gemspec +5 -3
  5. data/lib/hanami/view/cache.rb +16 -0
  6. data/lib/hanami/view/context.rb +15 -55
  7. data/lib/hanami/view/context_helpers/content_helpers.rb +5 -5
  8. data/lib/hanami/view/decorated_attributes.rb +2 -2
  9. data/lib/hanami/view/erb/engine.rb +27 -0
  10. data/lib/hanami/view/erb/filters/block.rb +44 -0
  11. data/lib/hanami/view/erb/filters/trimming.rb +42 -0
  12. data/lib/hanami/view/erb/parser.rb +161 -0
  13. data/lib/hanami/view/erb/template.rb +30 -0
  14. data/lib/hanami/view/errors.rb +8 -2
  15. data/lib/hanami/view/exposure.rb +23 -17
  16. data/lib/hanami/view/exposures.rb +22 -13
  17. data/lib/hanami/view/helpers/escape_helper.rb +221 -0
  18. data/lib/hanami/view/helpers/number_formatting_helper.rb +182 -0
  19. data/lib/hanami/view/helpers/tag_helper/tag_builder.rb +230 -0
  20. data/lib/hanami/view/helpers/tag_helper.rb +210 -0
  21. data/lib/hanami/view/html.rb +104 -0
  22. data/lib/hanami/view/html_safe_string_buffer.rb +46 -0
  23. data/lib/hanami/view/part.rb +13 -15
  24. data/lib/hanami/view/part_builder.rb +68 -108
  25. data/lib/hanami/view/path.rb +4 -31
  26. data/lib/hanami/view/renderer.rb +36 -44
  27. data/lib/hanami/view/rendering.rb +42 -0
  28. data/lib/hanami/view/{render_environment_missing.rb → rendering_missing.rb} +8 -13
  29. data/lib/hanami/view/scope.rb +14 -15
  30. data/lib/hanami/view/scope_builder.rb +42 -78
  31. data/lib/hanami/view/tilt/haml_adapter.rb +40 -0
  32. data/lib/hanami/view/tilt/slim_adapter.rb +40 -0
  33. data/lib/hanami/view/tilt.rb +22 -46
  34. data/lib/hanami/view/version.rb +1 -1
  35. data/lib/hanami/view.rb +53 -91
  36. metadata +64 -26
  37. data/LICENSE +0 -20
  38. data/lib/hanami/view/render_environment.rb +0 -62
  39. data/lib/hanami/view/tilt/erb.rb +0 -26
  40. data/lib/hanami/view/tilt/erbse.rb +0 -21
  41. data/lib/hanami/view/tilt/haml.rb +0 -26
@@ -0,0 +1,210 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Based on ActionView::Helpers::TagHelper, also released under the MIT licence.
4
+ #
5
+ # Copyright (c) David Heinemeier Hansson
6
+
7
+ module Hanami
8
+ class View
9
+ module Helpers
10
+ # Helper methods for generating HTML tags.
11
+ #
12
+ # When using full Hanami apps, these helpers will be automatically available in your view
13
+ # templates, part classes and scope classes.
14
+ #
15
+ # When using hanami-view standalone, include this module directly in your base part and scope
16
+ # classes, or in specific classes as required.
17
+ #
18
+ # @example Standalone usage
19
+ # class BasePart < Hanami::View::Part
20
+ # include Hanami::View::Helpers::TagHelper
21
+ # end
22
+ #
23
+ # class BaseScope < Hanami::View::Scope
24
+ # include Hanami::View::Helpers::TagHelper
25
+ # end
26
+ #
27
+ # class BaseView < Hanami::View
28
+ # config.part_class = BasePart
29
+ # config.scope_class = BaseScope
30
+ # end
31
+ #
32
+ # @api public
33
+ # @since 2.0.0
34
+ module TagHelper
35
+ module_function
36
+
37
+ # Returns a tag builder for building HTML tag strings.
38
+ #
39
+ # @example General usage
40
+ # tag.div # => <div></div>
41
+ # tag.img # => <img>
42
+ #
43
+ # tag.div("hello") # => <div>hello</div>
44
+ # tag.div { "hello" } # => <div>hello</div>
45
+ # tag.div(tag.p("hello")) # => <div><p>hello</p></div>
46
+ #
47
+ # tag.div(class: ["a", "b"]) # => <div class="a b"></div>
48
+ # tag.div(class: {"a": true, "b": false}) # => <div class="a"></div>
49
+ #
50
+ # tag.div(id: "el", data: {x: "y"}) # => <div id="el" data-x="y"></div>
51
+ # tag.div(id: "el", aria: {x: "y"}) # => <div id="el" aria-x="y"></div>
52
+ #
53
+ # tag.custom_tag("hello") # => <custom-tag>hello</custom-tag>
54
+ #
55
+ # @example Escaping
56
+ # tag.p("<script>alert()</script>") # => <p>&lt;script&gt;alert()&lt;/script&gt;</p>
57
+ # tag.p(class: "<script>alert()</script>") # => <p class="&lt;script&gt;alert()&lt;/script&gt;"></p>
58
+ # tag.p("<em>safe content</em>".html_safe) # => <p><em>safe content</em></p>
59
+ #
60
+ # @example Within templates
61
+ # <%= tag.div(id: "el") do %>
62
+ # <p>Template content can be mixed in.</p>
63
+ # <%= tag.p("Also nested tag builders.") %>
64
+ # <% end %>
65
+ #
66
+ # @api public
67
+ # @since 2.0.0
68
+ def tag
69
+ tag_builder
70
+ end
71
+
72
+ # Returns an anchor tag for the given contents and URL.
73
+ #
74
+ # The tag's contents are automatically escaped (unless marked as HTML safe).
75
+ #
76
+ # Uses the {#tag} builder to prepare the tag, so all tag builder options are also used.
77
+ #
78
+ # @overload link_to(content, url, **attributes)
79
+ # Returns a tag using a given string as the contents.
80
+ #
81
+ # @param content [String] content used in the a tag
82
+ # @param url [String] URL to be used in the `href` attribute
83
+ # @param attributes [Hash] HTML attributes to include in the tag
84
+ #
85
+ # @overload link_to(url, **attributes, &block)
86
+ # Returns a tag using the given block's return value as the contents.
87
+ #
88
+ # @param url [String] URL to be used in the `href` attribute
89
+ # @param attributes [Hash] HTML attributes to include in the tag
90
+ # @param block [Proc] block that returns the contents of the tag
91
+ #
92
+ # @return [String] HTML markup for the anchor tag
93
+ #
94
+ # @example
95
+ # link_to("Home", "/")
96
+ # # => <a href="/">Home</a>
97
+ #
98
+ # link_to("/") { "Home" }
99
+ # # => <a href="/">Home</a>
100
+ #
101
+ # link_to("Home", "/", class: "button") %>
102
+ # # => <a href="/" class="button">Home</a>
103
+ #
104
+ # @example Escaping
105
+ # link_to("<script>alert('xss')</script>", "/")
106
+ # # => <a href="/">&lt;script&gt;alert(&#39;xss&#39;)&lt;/script&gt;</a>
107
+ #
108
+ # link_to("/") { "<script>alert('xss')</script>" }
109
+ # # => <a href="/">&lt;script&gt;alert(&#39;xss&#39;)&lt;/script&gt;</a>
110
+ #
111
+ # @see #tag
112
+ #
113
+ # @api public
114
+ # @since 2.0.0
115
+ def link_to(content, url = nil, **attributes, &block)
116
+ if block
117
+ raise ArgumentError if url && content
118
+
119
+ url = content
120
+ content = nil
121
+ end
122
+
123
+ attributes[:href] = url or raise ArgumentError
124
+
125
+ tag.a(content, **attributes, &block)
126
+ end
127
+
128
+ # Returns a string of space-separated tokens from a range of given arguments.
129
+ #
130
+ # This is intended for building an HTML tag attribute value, such as a list of class names.
131
+ #
132
+ # @return [String]
133
+ #
134
+ # @example
135
+ # token_list("foo", "bar")
136
+ # # => "foo bar"
137
+ #
138
+ # token_list("foo", "foo bar")
139
+ # # => "foo bar"
140
+ #
141
+ # token_list({ foo: true, bar: false })
142
+ # # => "foo"
143
+ #
144
+ # token_list(nil, false, 123, "", "foo", { bar: true })
145
+ # # => "123 foo bar"
146
+ #
147
+ # @api public
148
+ # @since 2.0.0
149
+ def token_list(*args)
150
+ tokens = build_tag_values(*args).flat_map { |value|
151
+ safe = value.html_safe?
152
+ value.split(/\s+/).map { |s| safe ? s.html_safe : s }
153
+ }
154
+
155
+ EscapeHelper.escape_join(tokens, " ")
156
+ end
157
+
158
+ # @see #token_list
159
+ #
160
+ # @api public
161
+ # @since 2.0.0
162
+ def class_names(...)
163
+ token_list(...)
164
+ end
165
+
166
+ # @api private
167
+ # @since 2.0.0
168
+ def build_tag_values(*args)
169
+ tag_values = []
170
+
171
+ args.each do |tag_value|
172
+ case tag_value
173
+ when Hash
174
+ tag_value.each do |key, val|
175
+ tag_values << key.to_s if val && !key.to_s.empty?
176
+ end
177
+ when Array
178
+ tag_values.concat build_tag_values(*tag_value)
179
+ else
180
+ tag_values << tag_value.to_s unless tag_value.to_s.empty?
181
+ end
182
+ end
183
+
184
+ tag_values
185
+ end
186
+
187
+ # @api private
188
+ # @since 2.0.0
189
+ def tag_builder
190
+ @tag_builder ||= begin
191
+ TagBuilder.new(inflector: tag_builder_inflector)
192
+ end
193
+ end
194
+
195
+ # @api private
196
+ # @since 2.0.0
197
+ def tag_builder_inflector
198
+ if respond_to?(:_context)
199
+ return _context.inflector
200
+ end
201
+
202
+ # TODO: When hanami-view moves to Zeitwerk (and the only external require is for
203
+ # "dry/view"), remove this.
204
+ require "dry/inflector"
205
+ Dry::Inflector.new
206
+ end
207
+ end
208
+ end
209
+ end
210
+ end
@@ -0,0 +1,104 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Hanami
4
+ class View
5
+ module HTML
6
+ # A string that has been marked as "HTML safe", ensuring that it is not automatically escaped
7
+ # when used in HTML view templates.
8
+ #
9
+ # A SafeString is frozen when initialized to ensure it cannot be mutated after being marked as
10
+ # safe, which could be an avenue for injection of unsafe content.
11
+ #
12
+ # @see String#html_safe
13
+ #
14
+ # @api public
15
+ # @since 2.0.0
16
+ class SafeString < String
17
+ # @api public
18
+ # @since 2.0.0
19
+ def initialize(string)
20
+ super(string)
21
+ freeze
22
+ end
23
+
24
+ # @api private
25
+ private def initialize_copy(other)
26
+ super
27
+ freeze
28
+ end
29
+
30
+ # @return [true]
31
+ #
32
+ # @api public
33
+ # @since 2.0.0
34
+ def html_safe?
35
+ true
36
+ end
37
+
38
+ # @return [self]
39
+ #
40
+ # @api public
41
+ # @since 2.0.0
42
+ def html_safe
43
+ self
44
+ end
45
+
46
+ # @return [self]
47
+ #
48
+ # @api public
49
+ # @since 2.0.0
50
+ def to_s
51
+ self
52
+ end
53
+ end
54
+
55
+ # @api private
56
+ # @since 2.0.0
57
+ module StringExtensions
58
+ # Returns the string as a {Hanami::View::HTML::SafeString}, ensuring the string is not
59
+ # automatically escaped when used in HTML view templates.
60
+ #
61
+ # @return [Hanami::View::HTML::SafeString]
62
+ #
63
+ # @api public
64
+ # @since 2.0.0
65
+ def html_safe
66
+ Hanami::View::HTML::SafeString.new(self)
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
72
+
73
+ class String
74
+ # Prepend our `#html_safe` method so that it takes precedence over Active Support's. When both
75
+ # methods are loaded, the more likely scenario is that the user will want Hanami's, since in the
76
+ # context of a Hanami app, Active Support is more likely to be loaded incidentally, as a
77
+ # transitive dependency of another gem.
78
+ #
79
+ # Having our `#html_safe` available via this module also means that a user can also choose to
80
+ # _undefine_ this method within the module if they'd rather use Active Support's.
81
+ prepend Hanami::View::HTML::StringExtensions
82
+ end
83
+
84
+ class Object
85
+ # @return [false]
86
+ #
87
+ # @api public
88
+ # @since 2.0.0
89
+ def html_safe?
90
+ false
91
+ end
92
+ end
93
+
94
+ class Numeric
95
+ # @return [true]
96
+ #
97
+ # @api public
98
+ # @since 2.0.0
99
+ def html_safe?
100
+ true
101
+ end
102
+ end
103
+
104
+
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "temple"
4
+
5
+ module Hanami
6
+ class View
7
+ # Specialized Temple buffer class that marks block-captured strings as HTML safe.
8
+ #
9
+ # This is important for any scope or part methods that receive a string from a yielded block
10
+ # and then determine whether to escape that string based on its `.html_safe?` value.
11
+ #
12
+ # In this case, since blocks captured templates _intentionally_ contain HTML (this is the
13
+ # purpose of the template after all), it makes sense to mark the entire captured block string as
14
+ # HTML safe.
15
+ #
16
+ # This is compatible with escaping of values interpolated into the template, since those will
17
+ # have already been automatically escaped by the template engine when they are evaluated, before
18
+ # the overall block is captured.
19
+ #
20
+ # This filter is included in all three of our supported HTML template engines (ERB, Haml and
21
+ # Slim) to provide consistent behavior across all.
22
+ #
23
+ # @see Hanami::View::ERB::Engine
24
+ # @see Hanami::View::HamlAdapter::Template
25
+ # @see Hanami::View::SlimAdapter::Template
26
+ #
27
+ # @api private
28
+ # @since 2.0.0
29
+ class HTMLSafeStringBuffer < Temple::Generators::StringBuffer
30
+ # Replace `Temple::Generator::ArrayBuffer#call` (which is used via the superclass of
31
+ # `StringBuffer`) with the standard implementation from the base `Temple::Generator`.
32
+ #
33
+ # This avoids certain specialisations in `ArrayBuffer#call` that prevent `#return_buffer` from
34
+ # being called. For our needs, `#return_buffer` must be called at all times in order to ensure
35
+ # the captured string is consistently marked as `.html_safe`.
36
+ def call(exp)
37
+ [preamble, compile(exp), postamble].flatten.compact.join('; ')
38
+ end
39
+
40
+ # Marks the string returned from the captured buffer as HTML safe.
41
+ def return_buffer
42
+ "#{buffer}.html_safe"
43
+ end
44
+ end
45
+ end
46
+ end
@@ -1,8 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "dry/core/equalizer"
4
- require_relative "decorated_attributes"
5
- require_relative "render_environment_missing"
6
4
 
7
5
  module Hanami
8
6
  class View
@@ -25,7 +23,7 @@ module Hanami
25
23
  value
26
24
  ].freeze
27
25
 
28
- include Dry::Equalizer(:_name, :_value, :_render_env)
26
+ include Dry::Equalizer(:_name, :_value, :_rendering)
29
27
  include DecoratedAttributes
30
28
 
31
29
  # The part's name. This comes from the exposure supplying the value.
@@ -48,12 +46,12 @@ module Hanami
48
46
  # @api public
49
47
  attr_reader :_value
50
48
 
51
- # The current render environment
49
+ # The current rendering
52
50
  #
53
- # @return [RenderEnvironment] render environment
51
+ # @return [Rendering]
54
52
  #
55
53
  # @api private
56
- attr_reader :_render_env
54
+ attr_reader :_rendering
57
55
 
58
56
  # Determins a part name (when initialized without one). Intended for use
59
57
  # only while unit testing Parts.
@@ -67,17 +65,17 @@ module Hanami
67
65
  #
68
66
  # @param name [Symbol] part name
69
67
  # @param value [Object] the value to decorate
70
- # @param render_env [RenderEnvironment] render environment
68
+ # @param rendering [Rendering] the current rendering
71
69
  #
72
70
  # @api public
73
71
  def initialize(
74
- render_env: RenderEnvironmentMissing.new,
75
- name: self.class.part_name(render_env.inflector),
72
+ rendering: RenderingMissing.new,
73
+ name: self.class.part_name(rendering.inflector),
76
74
  value:
77
75
  )
78
76
  @_name = name
79
77
  @_value = value
80
- @_render_env = render_env
78
+ @_rendering = rendering
81
79
  end
82
80
 
83
81
  # The template format for the current render environment.
@@ -92,7 +90,7 @@ module Hanami
92
90
  #
93
91
  # @api public
94
92
  def _format
95
- _render_env.format
93
+ _rendering.format
96
94
  end
97
95
 
98
96
  # The context object for the current render environment
@@ -107,7 +105,7 @@ module Hanami
107
105
  #
108
106
  # @api public
109
107
  def _context
110
- _render_env.context
108
+ _rendering.context
111
109
  end
112
110
 
113
111
  # Renders a new partial with the part included in its locals.
@@ -128,7 +126,7 @@ module Hanami
128
126
  # @api public
129
127
  # rubocop:disable Naming/UncommunicativeMethodParamName
130
128
  def _render(partial_name, as: _name, **locals, &block)
131
- _render_env.partial(partial_name, _render_env.scope({as => self}.merge(locals)), &block)
129
+ _rendering.partial(partial_name, _rendering.scope({as => self}.merge(locals)), &block)
132
130
  end
133
131
  # rubocop:enable Naming/UncommunicativeMethodParamName
134
132
 
@@ -148,7 +146,7 @@ module Hanami
148
146
  #
149
147
  # @api public
150
148
  def _scope(scope_name = nil, **locals)
151
- _render_env.scope(scope_name, {_name => self}.merge(locals))
149
+ _rendering.scope(scope_name, {_name => self}.merge(locals))
152
150
  end
153
151
 
154
152
  # Returns a string representation of the value
@@ -180,7 +178,7 @@ module Hanami
180
178
  klass.new(
181
179
  name: name,
182
180
  value: value,
183
- render_env: _render_env,
181
+ rendering: _rendering,
184
182
  **options
185
183
  )
186
184
  end
@@ -1,139 +1,99 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "dry/core/cache"
4
- require "dry/core/equalizer"
5
- require_relative "part"
6
-
7
3
  module Hanami
8
4
  class View
9
5
  # Decorates exposure values with matching parts
10
6
  #
11
7
  # @api private
12
8
  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)
9
+ class << self
10
+ # Decorates an exposure value
11
+ #
12
+ # @param name [Symbol] exposure name
13
+ # @param value [Object] exposure value
14
+ # @param as [Symbol, nil] alternative name to use for part class resolution
15
+ #
16
+ # @return [Hanami::View::Part] decorated value
17
+ #
18
+ # @api private
19
+ def call(name, value, as: nil, rendering:)
20
+ builder = value.respond_to?(:to_ary) ? :build_collection_part : :build_part
21
+
22
+ send(builder, name: name, value: value, as: as, rendering: rendering)
23
+ end
53
24
 
54
- klass.new(
55
- name: name,
56
- value: value,
57
- render_env: render_env
58
- )
59
- end
25
+ private
60
26
 
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)
27
+ def build_part(name:, value:, as:, rendering:)
28
+ klass = part_class(name: name, as: as, rendering: rendering)
64
29
 
65
- arr = value.to_ary.map { |obj|
66
- build_part(item_name, obj, **options.merge(as: item_as))
67
- }
30
+ klass.new(name: name, value: value, rendering: rendering)
31
+ end
68
32
 
69
- build_part(name, arr, **options.merge(as: collection_as))
70
- end
33
+ def build_collection_part(name:, value:, as: nil, rendering:)
34
+ item_name, item_as = collection_item_name_as(name, as, inflector: rendering.inflector)
35
+ item_part_class = part_class(name: item_name, as: item_as, rendering: rendering)
71
36
 
72
- # rubocop:disable Lint/UnusedMethodArgument
73
- def collection_options(name:, **options)
74
- collection_as = options[:as].is_a?(Array) ? options[:as].first : nil
37
+ arr = value.to_ary.map { |item|
38
+ item_part_class.new(name: item_name, value: item, rendering: rendering)
39
+ }
75
40
 
76
- options.merge(as: collection_as)
77
- end
78
- # rubocop:enable Lint/UnusedMethodArgument
41
+ collection_as = as.is_a?(Array) ? as.first : nil
42
+ build_part(name: name, value: arr, as: collection_as, rendering: rendering)
43
+ end
79
44
 
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]
45
+ def collection_item_name_as(name, as, inflector:)
46
+ singular_name = inflector.singularize(name).to_sym
47
+ singular_as =
48
+ if as.is_a?(Array)
49
+ as.last if as.length > 1
50
+ else
51
+ as
52
+ end
53
+
54
+ if singular_as && !singular_as.is_a?(Class)
55
+ singular_as = inflector.singularize(singular_as.to_s)
87
56
  end
88
57
 
89
- if singular_as && !singular_as.is_a?(Class)
90
- singular_as = inflector.singularize(singular_as.to_s)
58
+ [singular_name, singular_as]
91
59
  end
92
60
 
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
61
+ def part_class(name:, as:, rendering:)
62
+ name = as || name
101
63
 
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)
64
+ if name.is_a?(Class)
65
+ name
66
+ else
67
+ View.cache.fetch_or_store(:part_class, name, rendering.config) do
68
+ resolve_part_class(name: name, rendering: rendering)
69
+ end
107
70
  end
108
71
  end
109
- end
110
72
 
111
- # rubocop:disable Metrics/PerceivedComplexity
112
- def resolve_part_class(name:, fallback_class:)
113
- return fallback_class unless namespace
73
+ # rubocop:disable Metrics/PerceivedComplexity
74
+ def resolve_part_class(name:, rendering:)
75
+ namespace = rendering.config.part_namespace
76
+ return rendering.config.part_class unless namespace
114
77
 
115
- name = inflector.camelize(name.to_s)
78
+ name = rendering.inflector.camelize(name.to_s)
116
79
 
117
- # Give autoloaders a chance to act
118
- begin
119
- klass = namespace.const_get(name)
120
- rescue NameError # rubocop:disable Lint/HandleExceptions
121
- end
80
+ # Give autoloaders a chance to act
81
+ begin
82
+ klass = namespace.const_get(name)
83
+ rescue NameError # rubocop:disable Lint/HandleExceptions
84
+ end
122
85
 
123
- if !klass && namespace.const_defined?(name, false)
124
- klass = namespace.const_get(name)
125
- end
86
+ if !klass && namespace.const_defined?(name, false)
87
+ klass = namespace.const_get(name)
88
+ end
126
89
 
127
- if klass && klass < Part
128
- klass
129
- else
130
- fallback_class
90
+ if klass && klass < Part
91
+ klass
92
+ else
93
+ rendering.config.part_class
94
+ end
131
95
  end
132
- end
133
- # rubocop:enable Metrics/PerceivedComplexity
134
-
135
- def inflector
136
- render_env.inflector
96
+ # rubocop:enable Metrics/PerceivedComplexity
137
97
  end
138
98
  end
139
99
  end