view_component 3.23.2 → 4.0.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.
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 +415 -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 +160 -155
  21. data/lib/view_component/collection.rb +19 -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 +31 -559
  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
@@ -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,11 +316,12 @@ 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)
319
+ if Rails::VERSION::MAJOR == 7 && Rails::VERSION::MINOR == 1
320
+ # Rails expects us to define `format` on all renderables,
321
+ # but we do not know the `format` of a ViewComponent until runtime.
322
+ def format
323
+ nil
324
+ end
282
325
  end
283
326
 
284
327
  # The current request. Use sparingly as doing so introduces coupling that
@@ -289,10 +332,9 @@ module ViewComponent
289
332
  __vc_request
290
333
  end
291
334
 
292
- # Enables consumers to override request/@request
293
- #
294
335
  # @private
295
336
  def __vc_request
337
+ # The current request (if present, as mailers/jobs/etc do not have a request)
296
338
  @__vc_request ||= controller.request if controller.respond_to?(:request)
297
339
  end
298
340
 
@@ -305,7 +347,9 @@ module ViewComponent
305
347
 
306
348
  @__vc_content =
307
349
  if __vc_render_in_block_provided?
308
- view_context.capture(self, &@__vc_render_in_block)
350
+ with_original_virtual_path do
351
+ view_context.capture(self, &@__vc_render_in_block)
352
+ end
309
353
  elsif __vc_content_set_by_with_content_defined?
310
354
  @__vc_content_set_by_with_content
311
355
  end
@@ -318,6 +362,14 @@ module ViewComponent
318
362
  __vc_render_in_block_provided? || __vc_content_set_by_with_content_defined?
319
363
  end
320
364
 
365
+ # @private
366
+ def with_original_virtual_path
367
+ @view_context.instance_variable_set(:@virtual_path, @old_virtual_path)
368
+ yield
369
+ ensure
370
+ @view_context.instance_variable_set(:@virtual_path, virtual_path)
371
+ end
372
+
321
373
  private
322
374
 
323
375
  attr_reader :view_context
@@ -330,12 +382,8 @@ module ViewComponent
330
382
  defined?(@__vc_content_set_by_with_content)
331
383
  end
332
384
 
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?
385
+ def __vc_maybe_escape_html(text)
386
+ return text if @current_template && !@current_template.html?
339
387
  return text if text.blank?
340
388
 
341
389
  if text.html_safe?
@@ -346,54 +394,18 @@ module ViewComponent
346
394
  end
347
395
  end
348
396
 
349
- def safe_output_preamble
350
- maybe_escape_html(output_preamble) do
397
+ def __vc_safe_output_preamble
398
+ __vc_maybe_escape_html(output_preamble) do
351
399
  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
400
  end
353
401
  end
354
402
 
355
- def safe_output_postamble
356
- maybe_escape_html(output_postamble) do
403
+ def __vc_safe_output_postamble
404
+ __vc_maybe_escape_html(output_postamble) do
357
405
  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
406
  end
359
407
  end
360
408
 
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
409
  # Configuration for generators.
398
410
  #
399
411
  # All options under this namespace default to `false` unless otherwise
@@ -451,6 +463,18 @@ module ViewComponent
451
463
  # ```
452
464
  #
453
465
  # Defaults to `false`.
466
+ #
467
+ # #### ßparent_class
468
+ #
469
+ # Parent class for generated components
470
+ #
471
+ # ```ruby
472
+ # config.view_component.generate.parent_class = "MyBaseComponent"
473
+ # ```
474
+ #
475
+ # Defaults to nil. If this is falsy, generators will use
476
+ # "ApplicationComponent" if defined, "ViewComponent::Base" otherwise.
477
+ #
454
478
 
455
479
  class << self
456
480
  # The file path of the component Ruby file.
@@ -519,50 +543,43 @@ module ViewComponent
519
543
  Collection.new(self, collection, spacer_component, **args)
520
544
  end
521
545
 
546
+ # @private
547
+ def __vc_compile(raise_errors: false, force: false)
548
+ __vc_compiler.compile(raise_errors: raise_errors, force: force)
549
+ end
550
+
522
551
  # @private
523
552
  def inherited(child)
524
553
  # Compile so child will inherit compiled `call_*` template methods that
525
554
  # `compile` defines
526
- compile
555
+ __vc_compile
527
556
 
528
557
  # Give the child its own personal #render_template_for to protect against the case when
529
558
  # eager loading is disabled and the parent component is rendered before the child. In
530
559
  # such a scenario, the parent will override ViewComponent::Base#render_template_for,
531
560
  # 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?
561
+ if !child.instance_methods(false).include?(:render_template_for) && !child.__vc_compiled?
533
562
  child.class_eval <<~RUBY, __FILE__, __LINE__ + 1
534
- def render_template_for(variant = nil, format = nil)
563
+ def render_template_for(requested_details)
535
564
  # Force compilation here so the compiler always redefines render_template_for.
536
565
  # 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)
566
+ self.class.__vc_compile(raise_errors: true, force: true)
567
+ # .__vc_compile replaces this method; call the new one
568
+ render_template_for(requested_details)
540
569
  end
541
570
  RUBY
542
571
  end
543
572
 
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
573
  # Derive the source location of the component Ruby file from the call stack.
551
574
  # We need to ignore `inherited` frames here as they indicate that `inherited`
552
575
  # has been re-defined by the consuming application, likely in ApplicationComponent.
553
576
  # We use `base_label` method here instead of `label` to avoid cases where the method
554
577
  # owner is included in a prefix like `ApplicationComponent.inherited`.
555
578
  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
579
+ child.virtual_path = child.name&.underscore
563
580
 
564
581
  # Set collection parameter to the extended component
565
- child.with_collection_parameter provided_collection_parameter
582
+ child.with_collection_parameter(__vc_provided_collection_parameter)
566
583
 
567
584
  if instance_methods(false).include?(:render_template_for)
568
585
  vc_ancestor_calls = defined?(@__vc_ancestor_calls) ? @__vc_ancestor_calls.dup : []
@@ -575,22 +592,17 @@ module ViewComponent
575
592
  end
576
593
 
577
594
  # @private
578
- def compiled?
579
- compiler.compiled?
580
- end
581
-
582
- # @private
583
- def ensure_compiled
584
- compile unless compiled?
595
+ def __vc_compiled?
596
+ __vc_compiler.compiled?
585
597
  end
586
598
 
587
599
  # @private
588
- def compile(raise_errors: false, force: false)
589
- compiler.compile(raise_errors: raise_errors, force: force)
600
+ def __vc_ensure_compiled
601
+ __vc_compile unless __vc_compiled?
590
602
  end
591
603
 
592
604
  # @private
593
- def compiler
605
+ def __vc_compiler
594
606
  @__vc_compiler ||= Compiler.new(self)
595
607
  end
596
608
 
@@ -602,8 +614,8 @@ module ViewComponent
602
614
  #
603
615
  # @param parameter [Symbol] The parameter name used when rendering elements of a collection.
604
616
  def with_collection_parameter(parameter)
605
- @provided_collection_parameter = parameter
606
- @initialize_parameters = nil
617
+ @__vc_provided_collection_parameter = parameter
618
+ @__vc_initialize_parameters = nil
607
619
  end
608
620
 
609
621
  # Strips trailing whitespace from templates before compiling them.
@@ -632,18 +644,11 @@ module ViewComponent
632
644
  # is accepted, as support for collection
633
645
  # rendering is optional.
634
646
  # @private
635
- def validate_collection_parameter!(validate_default: false)
636
- parameter = validate_default ? collection_parameter : provided_collection_parameter
647
+ def __vc_validate_collection_parameter!(validate_default: false)
648
+ parameter = validate_default ? __vc_collection_parameter : __vc_provided_collection_parameter
637
649
 
638
650
  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
651
+ return if __vc_initialize_parameter_names.include?(parameter) || __vc_splatted_keyword_argument_present?
647
652
 
648
653
  raise MissingCollectionArgumentError.new(name, parameter)
649
654
  end
@@ -652,58 +657,58 @@ module ViewComponent
652
657
  # invalid parameters that could override the framework's
653
658
  # methods.
654
659
  # @private
655
- def validate_initialization_parameters!
656
- return unless initialize_parameter_names.include?(RESERVED_PARAMETER)
660
+ def __vc_validate_initialization_parameters!
661
+ return unless __vc_initialize_parameter_names.include?(:content)
657
662
 
658
- raise ReservedParameterError.new(name, RESERVED_PARAMETER)
663
+ raise ReservedParameterError.new(name, :content)
659
664
  end
660
665
 
661
666
  # @private
662
- def collection_parameter
663
- provided_collection_parameter || name && name.demodulize.underscore.chomp("_component").to_sym
667
+ def __vc_collection_parameter
668
+ @__vc_provided_collection_parameter ||= name && name.demodulize.underscore.chomp("_component").to_sym
664
669
  end
665
670
 
666
671
  # @private
667
- def collection_counter_parameter
668
- :"#{collection_parameter}_counter"
672
+ def __vc_collection_counter_parameter
673
+ @__vc_collection_counter_parameter ||= :"#{__vc_collection_parameter}_counter"
669
674
  end
670
675
 
671
676
  # @private
672
- def counter_argument_present?
673
- initialize_parameter_names.include?(collection_counter_parameter)
677
+ def __vc_counter_argument_present?
678
+ __vc_initialize_parameter_names.include?(__vc_collection_counter_parameter)
674
679
  end
675
680
 
676
681
  # @private
677
- def collection_iteration_parameter
678
- :"#{collection_parameter}_iteration"
682
+ def __vc_collection_iteration_parameter
683
+ @__vc_collection_iteration_parameter ||= :"#{__vc_collection_parameter}_iteration"
679
684
  end
680
685
 
681
686
  # @private
682
- def iteration_argument_present?
683
- initialize_parameter_names.include?(collection_iteration_parameter)
687
+ def __vc_iteration_argument_present?
688
+ __vc_initialize_parameter_names.include?(__vc_collection_iteration_parameter)
684
689
  end
685
690
 
686
691
  private
687
692
 
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!
693
+ def __vc_splatted_keyword_argument_present?
694
+ __vc_initialize_parameters.flatten.include?(:keyrest)
691
695
  end
692
696
 
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)
697
+ def __vc_initialize_parameter_names
698
+ @__vc_initialize_parameter_names ||=
699
+ if respond_to?(:attribute_names)
700
+ attribute_names.map(&:to_sym)
701
+ else
702
+ __vc_initialize_parameters.map(&:last)
703
+ end
699
704
  end
700
705
 
701
- def initialize_parameters
702
- @initialize_parameters ||= instance_method(:initialize).parameters
706
+ def __vc_initialize_parameters
707
+ @__vc_initialize_parameters ||= instance_method(:initialize).parameters
703
708
  end
704
709
 
705
- def provided_collection_parameter
706
- @provided_collection_parameter ||= nil
710
+ def __vc_provided_collection_parameter
711
+ @__vc_provided_collection_parameter ||= nil
707
712
  end
708
713
  end
709
714
 
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "action_view/renderer/collection_renderer" if Rails.version.to_f >= 6.1
3
+ require "action_view/renderer/collection_renderer"
4
4
 
5
5
  module ViewComponent
6
6
  class Collection
@@ -9,25 +9,32 @@ module ViewComponent
9
9
 
10
10
  delegate :size, to: :@collection
11
11
 
12
- attr_accessor :__vc_original_view_context
13
-
14
- def set_original_view_context(view_context)
15
- self.__vc_original_view_context = view_context
16
- end
17
-
18
12
  def render_in(view_context, &block)
19
13
  components.map do |component|
20
- component.set_original_view_context(__vc_original_view_context)
21
14
  component.render_in(view_context, &block)
22
15
  end.join(rendered_spacer(view_context)).html_safe
23
16
  end
24
17
 
18
+ def each(&block)
19
+ components.each(&block)
20
+ end
21
+
22
+ if Rails::VERSION::MAJOR == 7 && Rails::VERSION::MINOR == 1
23
+ # Rails expects us to define `format` on all renderables,
24
+ # but we do not know the `format` of a ViewComponent until runtime.
25
+ def format
26
+ nil
27
+ end
28
+ end
29
+
30
+ private
31
+
25
32
  def components
26
33
  return @components if defined? @components
27
34
 
28
35
  iterator = ActionView::PartialIteration.new(@collection.size)
29
36
 
30
- component.validate_collection_parameter!(validate_default: true)
37
+ component.__vc_validate_collection_parameter!(validate_default: true)
31
38
 
32
39
  @components = @collection.map do |item|
33
40
  component.new(**component_options(item, iterator)).tap do |component|
@@ -36,18 +43,6 @@ module ViewComponent
36
43
  end
37
44
  end
38
45
 
39
- def each(&block)
40
- components.each(&block)
41
- end
42
-
43
- # Rails expects us to define `format` on all renderables,
44
- # but we do not know the `format` of a ViewComponent until runtime.
45
- def format
46
- nil
47
- end
48
-
49
- private
50
-
51
46
  def initialize(component, object, spacer_component, **options)
52
47
  @component = component
53
48
  @collection = collection_variable(object || [])
@@ -64,16 +59,15 @@ module ViewComponent
64
59
  end
65
60
 
66
61
  def component_options(item, iterator)
67
- item_options = {component.collection_parameter => item}
68
- item_options[component.collection_counter_parameter] = iterator.index if component.counter_argument_present?
69
- item_options[component.collection_iteration_parameter] = iterator.dup if component.iteration_argument_present?
62
+ item_options = {component.__vc_collection_parameter => item}
63
+ item_options[component.__vc_collection_counter_parameter] = iterator.index if component.__vc_counter_argument_present?
64
+ item_options[component.__vc_collection_iteration_parameter] = iterator.dup if component.__vc_iteration_argument_present?
70
65
 
71
66
  @options.merge(item_options)
72
67
  end
73
68
 
74
69
  def rendered_spacer(view_context)
75
70
  if @spacer_component
76
- @spacer_component.set_original_view_context(__vc_original_view_context)
77
71
  @spacer_component.render_in(view_context)
78
72
  else
79
73
  ""