view_component 3.23.2 → 4.0.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.
Files changed (66) hide show
  1. checksums.yaml +4 -4
  2. data/app/controllers/concerns/view_component/preview_actions.rb +11 -14
  3. data/app/controllers/view_components_system_test_controller.rb +15 -20
  4. data/app/views/test_mailer/test_asset_email.html.erb +1 -0
  5. data/app/views/test_mailer/test_url_email.html.erb +1 -0
  6. data/app/views/view_components/preview.html.erb +1 -9
  7. data/docs/CHANGELOG.md +404 -0
  8. data/lib/{rails/generators → generators/view_component}/abstract_generator.rb +2 -2
  9. data/lib/{rails/generators → generators/view_component}/component/component_generator.rb +16 -3
  10. data/lib/{rails/generators → generators/view_component}/component/templates/component.rb.tt +6 -1
  11. data/lib/{rails/generators/erb/component_generator.rb → generators/view_component/erb/erb_generator.rb} +4 -3
  12. data/lib/{rails/generators/haml/component_generator.rb → generators/view_component/haml/haml_generator.rb} +3 -3
  13. data/lib/{rails/generators/locale/component_generator.rb → generators/view_component/locale/locale_generator.rb} +3 -3
  14. data/lib/{rails/generators/preview/component_generator.rb → generators/view_component/preview/preview_generator.rb} +3 -3
  15. data/lib/{rails/generators/rspec/component_generator.rb → generators/view_component/rspec/rspec_generator.rb} +3 -3
  16. data/lib/{rails/generators/slim/component_generator.rb → generators/view_component/slim/slim_generator.rb} +3 -3
  17. data/lib/{rails/generators/stimulus/component_generator.rb → generators/view_component/stimulus/stimulus_generator.rb} +3 -3
  18. data/lib/generators/view_component/tailwindcss/tailwindcss_generator.rb +11 -0
  19. data/lib/{rails/generators/test_unit/component_generator.rb → generators/view_component/test_unit/test_unit_generator.rb} +2 -2
  20. data/lib/view_component/base.rb +154 -157
  21. data/lib/view_component/collection.rb +11 -25
  22. data/lib/view_component/compiler.rb +52 -79
  23. data/lib/view_component/config.rb +51 -85
  24. data/lib/view_component/configurable.rb +1 -1
  25. data/lib/view_component/deprecation.rb +1 -1
  26. data/lib/view_component/engine.rb +37 -107
  27. data/lib/view_component/errors.rb +16 -34
  28. data/lib/view_component/inline_template.rb +3 -4
  29. data/lib/view_component/instrumentation.rb +4 -10
  30. data/lib/view_component/preview.rb +4 -11
  31. data/lib/view_component/request_details.rb +30 -0
  32. data/lib/view_component/slot.rb +6 -13
  33. data/lib/view_component/slotable.rb +82 -77
  34. data/lib/view_component/system_spec_helpers.rb +11 -0
  35. data/lib/view_component/system_test_helpers.rb +1 -2
  36. data/lib/view_component/template.rb +106 -83
  37. data/lib/view_component/test_helpers.rb +37 -44
  38. data/lib/view_component/translatable.rb +33 -32
  39. data/lib/view_component/version.rb +3 -3
  40. data/lib/view_component.rb +8 -6
  41. metadata +30 -558
  42. data/app/assets/vendor/prism.css +0 -4
  43. data/app/assets/vendor/prism.min.js +0 -12
  44. data/app/helpers/preview_helper.rb +0 -85
  45. data/app/views/view_components/_preview_source.html.erb +0 -17
  46. data/lib/rails/generators/tailwindcss/component_generator.rb +0 -11
  47. data/lib/view_component/capture_compatibility.rb +0 -44
  48. data/lib/view_component/component_error.rb +0 -6
  49. data/lib/view_component/rails/tasks/view_component.rake +0 -20
  50. data/lib/view_component/render_component_helper.rb +0 -10
  51. data/lib/view_component/render_component_to_string_helper.rb +0 -9
  52. data/lib/view_component/render_monkey_patch.rb +0 -13
  53. data/lib/view_component/render_to_string_monkey_patch.rb +0 -13
  54. data/lib/view_component/rendering_component_helper.rb +0 -9
  55. data/lib/view_component/rendering_monkey_patch.rb +0 -13
  56. data/lib/view_component/slotable_default.rb +0 -20
  57. data/lib/view_component/use_helpers.rb +0 -42
  58. /data/lib/{rails/generators → generators/view_component}/erb/templates/component.html.erb.tt +0 -0
  59. /data/lib/{rails/generators → generators/view_component}/haml/templates/component.html.haml.tt +0 -0
  60. /data/lib/{rails/generators → generators/view_component}/preview/templates/component_preview.rb.tt +0 -0
  61. /data/lib/{rails/generators → generators/view_component}/rspec/templates/component_spec.rb.tt +0 -0
  62. /data/lib/{rails/generators → generators/view_component}/slim/templates/component.html.slim.tt +0 -0
  63. /data/lib/{rails/generators → generators/view_component}/stimulus/templates/component_controller.js.tt +0 -0
  64. /data/lib/{rails/generators → generators/view_component}/stimulus/templates/component_controller.ts.tt +0 -0
  65. /data/lib/{rails/generators → generators/view_component}/tailwindcss/templates/component.html.erb.tt +0 -0
  66. /data/lib/{rails/generators → generators/view_component}/test_unit/templates/component_test.rb.tt +0 -0
@@ -1,10 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "rails/generators/abstract_generator"
3
+ require "generators/view_component/abstract_generator"
4
4
 
5
- module Locale
5
+ module ViewComponent
6
6
  module Generators
7
- class ComponentGenerator < ::Rails::Generators::NamedBase
7
+ class LocaleGenerator < ::Rails::Generators::NamedBase
8
8
  include ViewComponent::AbstractGenerator
9
9
 
10
10
  source_root File.expand_path("templates", __dir__)
@@ -1,8 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module Preview
3
+ module ViewComponent
4
4
  module Generators
5
- class ComponentGenerator < ::Rails::Generators::NamedBase
5
+ class PreviewGenerator < ::Rails::Generators::NamedBase
6
6
  source_root File.expand_path("templates", __dir__)
7
7
  class_option :preview_path, type: :string, desc: "Path for previews, required when multiple preview paths are configured", default: ViewComponent::Base.config.generate.preview_path
8
8
 
@@ -10,7 +10,7 @@ module Preview
10
10
  check_class_collision suffix: "ComponentPreview"
11
11
 
12
12
  def create_preview_file
13
- preview_paths = ViewComponent::Base.config.preview_paths
13
+ preview_paths = ViewComponent::Base.config.previews.paths
14
14
  optional_path = options[:preview_path]
15
15
  return if preview_paths.count > 1 && optional_path.blank?
16
16
 
@@ -1,10 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "rails/generators/abstract_generator"
3
+ require "generators/view_component/abstract_generator"
4
4
 
5
- module Rspec
5
+ module ViewComponent
6
6
  module Generators
7
- class ComponentGenerator < ::Rails::Generators::NamedBase
7
+ class RspecGenerator < ::Rails::Generators::NamedBase
8
8
  include ViewComponent::AbstractGenerator
9
9
 
10
10
  source_root File.expand_path("templates", __dir__)
@@ -1,10 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "rails/generators/erb/component_generator"
3
+ require "generators/view_component/erb/erb_generator"
4
4
 
5
- module Slim
5
+ module ViewComponent
6
6
  module Generators
7
- class ComponentGenerator < Erb::Generators::ComponentGenerator
7
+ class SlimGenerator < ViewComponent::Generators::ErbGenerator
8
8
  include ViewComponent::AbstractGenerator
9
9
 
10
10
  source_root File.expand_path("templates", __dir__)
@@ -1,10 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "rails/generators/abstract_generator"
3
+ require "generators/view_component/abstract_generator"
4
4
 
5
- module Stimulus
5
+ module ViewComponent
6
6
  module Generators
7
- class ComponentGenerator < ::Rails::Generators::NamedBase
7
+ class StimulusGenerator < ::Rails::Generators::NamedBase
8
8
  include ViewComponent::AbstractGenerator
9
9
 
10
10
  source_root File.expand_path("templates", __dir__)
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "generators/view_component/erb/erb_generator"
4
+
5
+ module ViewComponent
6
+ module Generators
7
+ class TailwindcssGenerator < ViewComponent::Generators::ErbGenerator
8
+ source_root File.expand_path("templates", __dir__)
9
+ end
10
+ end
11
+ end
@@ -1,8 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module TestUnit
3
+ module ViewComponent
4
4
  module Generators
5
- class ComponentGenerator < ::Rails::Generators::NamedBase
5
+ class TestUnitGenerator < ::Rails::Generators::NamedBase
6
6
  source_root File.expand_path("templates", __dir__)
7
7
  check_class_collision suffix: "ComponentTest"
8
8
 
@@ -9,15 +9,27 @@ require "view_component/config"
9
9
  require "view_component/errors"
10
10
  require "view_component/inline_template"
11
11
  require "view_component/preview"
12
+ require "view_component/request_details"
12
13
  require "view_component/slotable"
13
- require "view_component/slotable_default"
14
14
  require "view_component/template"
15
15
  require "view_component/translatable"
16
16
  require "view_component/with_content_helper"
17
- require "view_component/use_helpers"
17
+
18
+ module ActionView
19
+ class OutputBuffer
20
+ def with_buffer(buf = nil)
21
+ new_buffer = buf || +""
22
+ old_buffer, @raw_buffer = @raw_buffer, new_buffer
23
+ yield
24
+ new_buffer
25
+ ensure
26
+ @raw_buffer = old_buffer
27
+ end
28
+ end
29
+ end
18
30
 
19
31
  module ViewComponent
20
- class Base < ActionView::Base
32
+ class Base
21
33
  class << self
22
34
  delegate(*ViewComponent::Config.defaults.keys, to: :config)
23
35
 
@@ -34,26 +46,33 @@ module ViewComponent
34
46
  end
35
47
  end
36
48
 
49
+ include ActionView::Helpers
50
+ include Rails.application.routes.url_helpers if defined?(Rails) && Rails.application
51
+ include ERB::Escape
52
+ include ActiveSupport::CoreExt::ERBUtil
53
+
37
54
  include ViewComponent::InlineTemplate
38
- include ViewComponent::UseHelpers
39
55
  include ViewComponent::Slotable
40
56
  include ViewComponent::Translatable
41
57
  include ViewComponent::WithContentHelper
42
58
 
43
- RESERVED_PARAMETER = :content
44
- VC_INTERNAL_DEFAULT_FORMAT = :html
45
-
46
59
  # For CSRF authenticity tokens in forms
47
60
  delegate :form_authenticity_token, :protect_against_forgery?, :config, to: :helpers
48
61
 
62
+ # HTML construction methods
63
+ delegate :output_buffer, :lookup_context, :view_renderer, :view_flow, to: :helpers
64
+
65
+ # For Turbo::StreamsHelper
66
+ delegate :formats, :formats=, to: :helpers
67
+
49
68
  # For Content Security Policy nonces
50
69
  delegate :content_security_policy_nonce, to: :helpers
51
70
 
52
71
  # Config option that strips trailing whitespace in templates before compiling them.
53
- class_attribute :__vc_strip_trailing_whitespace, instance_accessor: false, instance_predicate: false
54
- self.__vc_strip_trailing_whitespace = false # class_attribute:default doesn't work until Rails 5.2
72
+ class_attribute :__vc_strip_trailing_whitespace, instance_accessor: false, instance_predicate: false, default: false
55
73
 
56
74
  attr_accessor :__vc_original_view_context
75
+ attr_reader :current_template
57
76
 
58
77
  # Components render in their own view context. Helpers and other functionality
59
78
  # require a reference to the original Rails view context, an instance of
@@ -65,7 +84,14 @@ module ViewComponent
65
84
  # @param view_context [ActionView::Base] The original view context.
66
85
  # @return [void]
67
86
  def set_original_view_context(view_context)
68
- self.__vc_original_view_context = view_context
87
+ # noop
88
+ end
89
+
90
+ using RequestDetails
91
+
92
+ # Including `Rails.application.routes.url_helpers` defines an initializer that accepts (...),
93
+ # so we have to define our own empty initializer to overwrite it.
94
+ def initialize
69
95
  end
70
96
 
71
97
  # Entrypoint for rendering components.
@@ -77,31 +103,28 @@ module ViewComponent
77
103
  #
78
104
  # @return [String]
79
105
  def render_in(view_context, &block)
80
- self.class.compile(raise_errors: true)
106
+ self.class.__vc_compile(raise_errors: true)
81
107
 
82
108
  @view_context = view_context
109
+ @old_virtual_path = view_context.instance_variable_get(:@virtual_path)
83
110
  self.__vc_original_view_context ||= view_context
84
111
 
85
- @output_buffer = ActionView::OutputBuffer.new
112
+ @output_buffer = view_context.output_buffer
86
113
 
87
114
  @lookup_context ||= view_context.lookup_context
88
115
 
89
- # required for path helpers in older Rails versions
90
- @view_renderer ||= view_context.view_renderer
91
-
92
116
  # For content_for
93
117
  @view_flow ||= view_context.view_flow
94
118
 
95
119
  # For i18n
96
120
  @virtual_path ||= virtual_path
97
121
 
98
- # For template variants (+phone, +desktop, etc.)
99
- @__vc_variant ||= @lookup_context.variants.first
122
+ # Describes the inferred request constraints (locales, formats, variants)
123
+ @__vc_requested_details ||= @lookup_context.vc_requested_details
100
124
 
101
125
  # For caching, such as #cache_if
102
126
  @current_template = nil unless defined?(@current_template)
103
127
  old_current_template = @current_template
104
- @current_template = self
105
128
 
106
129
  if block && defined?(@__vc_content_set_by_with_content)
107
130
  raise DuplicateContentError.new(self.class.name)
@@ -113,18 +136,35 @@ module ViewComponent
113
136
  before_render
114
137
 
115
138
  if render?
116
- rendered_template = render_template_for(@__vc_variant, __vc_request&.format&.to_sym).to_s
139
+ value = nil
140
+
141
+ @output_buffer.with_buffer do
142
+ @view_context.instance_variable_set(:@virtual_path, virtual_path)
143
+
144
+ rendered_template =
145
+ around_render do
146
+ render_template_for(@__vc_requested_details).to_s
147
+ end
148
+
149
+ # Avoid allocating new string when output_preamble and output_postamble are blank
150
+ value = if output_preamble.blank? && output_postamble.blank?
151
+ rendered_template
152
+ else
153
+ __vc_safe_output_preamble + rendered_template + __vc_safe_output_postamble
154
+ end
155
+ end
117
156
 
118
- # Avoid allocating new string when output_preamble and output_postamble are blank
119
- if output_preamble.blank? && output_postamble.blank?
120
- rendered_template
121
- else
122
- safe_output_preamble + rendered_template + safe_output_postamble
157
+ if ActionView::Base.annotate_rendered_view_with_filenames && current_template.inline_call? && request&.format == :html
158
+ identifier = defined?(Rails.root) ? self.class.identifier.sub("#{Rails.root}/", "") : self.class.identifier
159
+ value = "<!-- BEGIN #{identifier} -->".html_safe + value + "<!-- END #{identifier} -->".html_safe
123
160
  end
161
+
162
+ value
124
163
  else
125
164
  ""
126
165
  end
127
166
  ensure
167
+ view_context.instance_variable_set(:@virtual_path, @old_virtual_path)
128
168
  @current_template = old_current_template
129
169
  end
130
170
 
@@ -161,7 +201,7 @@ module ViewComponent
161
201
  target_render = self.class.instance_variable_get(:@__vc_ancestor_calls)[@__vc_parent_render_level]
162
202
  @__vc_parent_render_level += 1
163
203
 
164
- target_render.bind_call(self, @__vc_variant)
204
+ target_render.bind_call(self, @__vc_requested_details)
165
205
  ensure
166
206
  @__vc_parent_render_level -= 1
167
207
  end
@@ -189,6 +229,14 @@ module ViewComponent
189
229
  # noop
190
230
  end
191
231
 
232
+ # Called around rendering the component. Override to wrap the rendering of a
233
+ # component in custom instrumentation, etc.
234
+ #
235
+ # @return [void]
236
+ def around_render
237
+ yield
238
+ end
239
+
192
240
  # Override to determine whether the ViewComponent should render.
193
241
  #
194
242
  # @return [Boolean]
@@ -196,23 +244,17 @@ module ViewComponent
196
244
  true
197
245
  end
198
246
 
199
- # Override the ActionView::Base initializer so that components
200
- # do not need to define their own initializers.
201
- # @private
202
- def initialize(*)
203
- end
204
-
205
247
  # Re-use original view_context if we're not rendering a component.
206
248
  #
207
249
  # This prevents an exception when rendering a partial inside of a component that has also been rendered outside
208
250
  # of the component. This is due to the partials compiled template method existing in the parent `view_context`,
209
- # and not the component's `view_context`.
251
+ # and not the component's `view_context`.
210
252
  #
211
253
  # @private
212
254
  def render(options = {}, args = {}, &block)
213
255
  if options.respond_to?(:set_original_view_context)
214
256
  options.set_original_view_context(self.__vc_original_view_context)
215
- super
257
+ @view_context.render(options, args, &block)
216
258
  else
217
259
  __vc_original_view_context.render(options, args, &block)
218
260
  end
@@ -274,13 +316,6 @@ module ViewComponent
274
316
  []
275
317
  end
276
318
 
277
- # For caching, such as #cache_if
278
- #
279
- # @private
280
- def format
281
- @__vc_variant if defined?(@__vc_variant)
282
- end
283
-
284
319
  # The current request. Use sparingly as doing so introduces coupling that
285
320
  # inhibits encapsulation & reuse, often making testing difficult.
286
321
  #
@@ -289,10 +324,9 @@ module ViewComponent
289
324
  __vc_request
290
325
  end
291
326
 
292
- # Enables consumers to override request/@request
293
- #
294
327
  # @private
295
328
  def __vc_request
329
+ # The current request (if present, as mailers/jobs/etc do not have a request)
296
330
  @__vc_request ||= controller.request if controller.respond_to?(:request)
297
331
  end
298
332
 
@@ -305,7 +339,9 @@ module ViewComponent
305
339
 
306
340
  @__vc_content =
307
341
  if __vc_render_in_block_provided?
308
- view_context.capture(self, &@__vc_render_in_block)
342
+ with_original_virtual_path do
343
+ view_context.capture(self, &@__vc_render_in_block)
344
+ end
309
345
  elsif __vc_content_set_by_with_content_defined?
310
346
  @__vc_content_set_by_with_content
311
347
  end
@@ -318,6 +354,14 @@ module ViewComponent
318
354
  __vc_render_in_block_provided? || __vc_content_set_by_with_content_defined?
319
355
  end
320
356
 
357
+ # @private
358
+ def with_original_virtual_path
359
+ @view_context.instance_variable_set(:@virtual_path, @old_virtual_path)
360
+ yield
361
+ ensure
362
+ @view_context.instance_variable_set(:@virtual_path, virtual_path)
363
+ end
364
+
321
365
  private
322
366
 
323
367
  attr_reader :view_context
@@ -330,12 +374,8 @@ module ViewComponent
330
374
  defined?(@__vc_content_set_by_with_content)
331
375
  end
332
376
 
333
- def content_evaluated?
334
- defined?(@__vc_content_evaluated) && @__vc_content_evaluated
335
- end
336
-
337
- def maybe_escape_html(text)
338
- return text if __vc_request && !__vc_request.format.html?
377
+ def __vc_maybe_escape_html(text)
378
+ return text if @current_template && !@current_template.html?
339
379
  return text if text.blank?
340
380
 
341
381
  if text.html_safe?
@@ -346,54 +386,18 @@ module ViewComponent
346
386
  end
347
387
  end
348
388
 
349
- def safe_output_preamble
350
- maybe_escape_html(output_preamble) do
389
+ def __vc_safe_output_preamble
390
+ __vc_maybe_escape_html(output_preamble) do
351
391
  Kernel.warn("WARNING: The #{self.class} component was provided an HTML-unsafe preamble. The preamble will be automatically escaped, but you may want to investigate.")
352
392
  end
353
393
  end
354
394
 
355
- def safe_output_postamble
356
- maybe_escape_html(output_postamble) do
395
+ def __vc_safe_output_postamble
396
+ __vc_maybe_escape_html(output_postamble) do
357
397
  Kernel.warn("WARNING: The #{self.class} component was provided an HTML-unsafe postamble. The postamble will be automatically escaped, but you may want to investigate.")
358
398
  end
359
399
  end
360
400
 
361
- # Set the controller used for testing components:
362
- #
363
- # ```ruby
364
- # config.view_component.test_controller = "MyTestController"
365
- # ```
366
- #
367
- # Defaults to `nil`. If this is falsy, `"ApplicationController"` is used. Can also be
368
- # configured on a per-test basis using `with_controller_class`.
369
- #
370
-
371
- # Set if render monkey patches should be included or not in Rails <6.1:
372
- #
373
- # ```ruby
374
- # config.view_component.render_monkey_patch_enabled = false
375
- # ```
376
- #
377
-
378
- # Path for component files
379
- #
380
- # ```ruby
381
- # config.view_component.view_component_path = "app/my_components"
382
- # ```
383
- #
384
- # Defaults to `nil`. If this is falsy, `app/components` is used.
385
- #
386
-
387
- # Parent class for generated components
388
- #
389
- # ```ruby
390
- # config.view_component.component_parent_class = "MyBaseComponent"
391
- # ```
392
- #
393
- # Defaults to nil. If this is falsy, generators will use
394
- # "ApplicationComponent" if defined, "ViewComponent::Base" otherwise.
395
- #
396
-
397
401
  # Configuration for generators.
398
402
  #
399
403
  # All options under this namespace default to `false` unless otherwise
@@ -451,6 +455,18 @@ module ViewComponent
451
455
  # ```
452
456
  #
453
457
  # Defaults to `false`.
458
+ #
459
+ # #### ßparent_class
460
+ #
461
+ # Parent class for generated components
462
+ #
463
+ # ```ruby
464
+ # config.view_component.generate.parent_class = "MyBaseComponent"
465
+ # ```
466
+ #
467
+ # Defaults to nil. If this is falsy, generators will use
468
+ # "ApplicationComponent" if defined, "ViewComponent::Base" otherwise.
469
+ #
454
470
 
455
471
  class << self
456
472
  # The file path of the component Ruby file.
@@ -519,50 +535,43 @@ module ViewComponent
519
535
  Collection.new(self, collection, spacer_component, **args)
520
536
  end
521
537
 
538
+ # @private
539
+ def __vc_compile(raise_errors: false, force: false)
540
+ __vc_compiler.compile(raise_errors: raise_errors, force: force)
541
+ end
542
+
522
543
  # @private
523
544
  def inherited(child)
524
545
  # Compile so child will inherit compiled `call_*` template methods that
525
546
  # `compile` defines
526
- compile
547
+ __vc_compile
527
548
 
528
549
  # Give the child its own personal #render_template_for to protect against the case when
529
550
  # eager loading is disabled and the parent component is rendered before the child. In
530
551
  # such a scenario, the parent will override ViewComponent::Base#render_template_for,
531
552
  # meaning it will not be called for any children and thus not compile their templates.
532
- if !child.instance_methods(false).include?(:render_template_for) && !child.compiled?
553
+ if !child.instance_methods(false).include?(:render_template_for) && !child.__vc_compiled?
533
554
  child.class_eval <<~RUBY, __FILE__, __LINE__ + 1
534
- def render_template_for(variant = nil, format = nil)
555
+ def render_template_for(requested_details)
535
556
  # Force compilation here so the compiler always redefines render_template_for.
536
557
  # This is mostly a safeguard to prevent infinite recursion.
537
- self.class.compile(raise_errors: true, force: true)
538
- # .compile replaces this method; call the new one
539
- render_template_for(variant, format)
558
+ self.class.__vc_compile(raise_errors: true, force: true)
559
+ # .__vc_compile replaces this method; call the new one
560
+ render_template_for(requested_details)
540
561
  end
541
562
  RUBY
542
563
  end
543
564
 
544
- # If Rails application is loaded, add application url_helpers to the component context
545
- # we need to check this to use this gem as a dependency
546
- if defined?(Rails) && Rails.application && !(child < Rails.application.routes.url_helpers)
547
- child.include Rails.application.routes.url_helpers
548
- end
549
-
550
565
  # Derive the source location of the component Ruby file from the call stack.
551
566
  # We need to ignore `inherited` frames here as they indicate that `inherited`
552
567
  # has been re-defined by the consuming application, likely in ApplicationComponent.
553
568
  # We use `base_label` method here instead of `label` to avoid cases where the method
554
569
  # owner is included in a prefix like `ApplicationComponent.inherited`.
555
570
  child.identifier = caller_locations(1, 10).reject { |l| l.base_label == "inherited" }[0].path
556
-
557
- # If Rails application is loaded, removes the first part of the path and the extension.
558
- if defined?(Rails) && Rails.application
559
- child.virtual_path = child.identifier.gsub(
560
- /(.*#{Regexp.quote(ViewComponent::Base.config.view_component_path)})|(\.rb)/, ""
561
- )
562
- end
571
+ child.virtual_path = child.name&.underscore
563
572
 
564
573
  # Set collection parameter to the extended component
565
- child.with_collection_parameter provided_collection_parameter
574
+ child.with_collection_parameter(__vc_provided_collection_parameter)
566
575
 
567
576
  if instance_methods(false).include?(:render_template_for)
568
577
  vc_ancestor_calls = defined?(@__vc_ancestor_calls) ? @__vc_ancestor_calls.dup : []
@@ -575,22 +584,17 @@ module ViewComponent
575
584
  end
576
585
 
577
586
  # @private
578
- def compiled?
579
- compiler.compiled?
580
- end
581
-
582
- # @private
583
- def ensure_compiled
584
- compile unless compiled?
587
+ def __vc_compiled?
588
+ __vc_compiler.compiled?
585
589
  end
586
590
 
587
591
  # @private
588
- def compile(raise_errors: false, force: false)
589
- compiler.compile(raise_errors: raise_errors, force: force)
592
+ def __vc_ensure_compiled
593
+ __vc_compile unless __vc_compiled?
590
594
  end
591
595
 
592
596
  # @private
593
- def compiler
597
+ def __vc_compiler
594
598
  @__vc_compiler ||= Compiler.new(self)
595
599
  end
596
600
 
@@ -602,8 +606,8 @@ module ViewComponent
602
606
  #
603
607
  # @param parameter [Symbol] The parameter name used when rendering elements of a collection.
604
608
  def with_collection_parameter(parameter)
605
- @provided_collection_parameter = parameter
606
- @initialize_parameters = nil
609
+ @__vc_provided_collection_parameter = parameter
610
+ @__vc_initialize_parameters = nil
607
611
  end
608
612
 
609
613
  # Strips trailing whitespace from templates before compiling them.
@@ -632,18 +636,11 @@ module ViewComponent
632
636
  # is accepted, as support for collection
633
637
  # rendering is optional.
634
638
  # @private
635
- def validate_collection_parameter!(validate_default: false)
636
- parameter = validate_default ? collection_parameter : provided_collection_parameter
639
+ def __vc_validate_collection_parameter!(validate_default: false)
640
+ parameter = validate_default ? __vc_collection_parameter : __vc_provided_collection_parameter
637
641
 
638
642
  return unless parameter
639
- return if initialize_parameter_names.include?(parameter) || splatted_keyword_argument_present?
640
-
641
- # If Ruby can't parse the component class, then the initialize
642
- # parameters will be empty and ViewComponent will not be able to render
643
- # the component.
644
- if initialize_parameters.empty?
645
- raise EmptyOrInvalidInitializerError.new(name, parameter)
646
- end
643
+ return if __vc_initialize_parameter_names.include?(parameter) || __vc_splatted_keyword_argument_present?
647
644
 
648
645
  raise MissingCollectionArgumentError.new(name, parameter)
649
646
  end
@@ -652,58 +649,58 @@ module ViewComponent
652
649
  # invalid parameters that could override the framework's
653
650
  # methods.
654
651
  # @private
655
- def validate_initialization_parameters!
656
- return unless initialize_parameter_names.include?(RESERVED_PARAMETER)
652
+ def __vc_validate_initialization_parameters!
653
+ return unless __vc_initialize_parameter_names.include?(:content)
657
654
 
658
- raise ReservedParameterError.new(name, RESERVED_PARAMETER)
655
+ raise ReservedParameterError.new(name, :content)
659
656
  end
660
657
 
661
658
  # @private
662
- def collection_parameter
663
- provided_collection_parameter || name && name.demodulize.underscore.chomp("_component").to_sym
659
+ def __vc_collection_parameter
660
+ @__vc_provided_collection_parameter ||= name && name.demodulize.underscore.chomp("_component").to_sym
664
661
  end
665
662
 
666
663
  # @private
667
- def collection_counter_parameter
668
- :"#{collection_parameter}_counter"
664
+ def __vc_collection_counter_parameter
665
+ @__vc_collection_counter_parameter ||= :"#{__vc_collection_parameter}_counter"
669
666
  end
670
667
 
671
668
  # @private
672
- def counter_argument_present?
673
- initialize_parameter_names.include?(collection_counter_parameter)
669
+ def __vc_counter_argument_present?
670
+ __vc_initialize_parameter_names.include?(__vc_collection_counter_parameter)
674
671
  end
675
672
 
676
673
  # @private
677
- def collection_iteration_parameter
678
- :"#{collection_parameter}_iteration"
674
+ def __vc_collection_iteration_parameter
675
+ @__vc_collection_iteration_parameter ||= :"#{__vc_collection_parameter}_iteration"
679
676
  end
680
677
 
681
678
  # @private
682
- def iteration_argument_present?
683
- initialize_parameter_names.include?(collection_iteration_parameter)
679
+ def __vc_iteration_argument_present?
680
+ __vc_initialize_parameter_names.include?(__vc_collection_iteration_parameter)
684
681
  end
685
682
 
686
683
  private
687
684
 
688
- def splatted_keyword_argument_present?
689
- initialize_parameters.flatten.include?(:keyrest) &&
690
- !initialize_parameters.include?([:keyrest, :**]) # Un-named splatted keyword args don't count!
685
+ def __vc_splatted_keyword_argument_present?
686
+ __vc_initialize_parameters.flatten.include?(:keyrest)
691
687
  end
692
688
 
693
- def initialize_parameter_names
694
- return attribute_names.map(&:to_sym) if respond_to?(:attribute_names)
695
-
696
- return attribute_types.keys.map(&:to_sym) if Rails::VERSION::MAJOR <= 5 && respond_to?(:attribute_types)
697
-
698
- initialize_parameters.map(&:last)
689
+ def __vc_initialize_parameter_names
690
+ @__vc_initialize_parameter_names ||=
691
+ if respond_to?(:attribute_names)
692
+ attribute_names.map(&:to_sym)
693
+ else
694
+ __vc_initialize_parameters.map(&:last)
695
+ end
699
696
  end
700
697
 
701
- def initialize_parameters
702
- @initialize_parameters ||= instance_method(:initialize).parameters
698
+ def __vc_initialize_parameters
699
+ @__vc_initialize_parameters ||= instance_method(:initialize).parameters
703
700
  end
704
701
 
705
- def provided_collection_parameter
706
- @provided_collection_parameter ||= nil
702
+ def __vc_provided_collection_parameter
703
+ @__vc_provided_collection_parameter ||= nil
707
704
  end
708
705
  end
709
706