view_component 3.25.0 → 4.0.0.alpha1

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 (60) hide show
  1. checksums.yaml +4 -4
  2. data/app/controllers/concerns/view_component/preview_actions.rb +2 -9
  3. data/app/controllers/view_components_system_test_controller.rb +17 -29
  4. data/app/views/test_mailer/test_asset_email.html.erb +1 -0
  5. data/app/views/view_components/preview.html.erb +1 -9
  6. data/docs/CHANGELOG.md +77 -17
  7. data/lib/view_component/base.rb +49 -100
  8. data/lib/view_component/collection.rb +12 -19
  9. data/lib/view_component/compiler.rb +50 -74
  10. data/lib/view_component/config.rb +0 -21
  11. data/lib/view_component/deprecation.rb +1 -1
  12. data/lib/view_component/engine.rb +3 -82
  13. data/lib/view_component/errors.rb +16 -22
  14. data/lib/view_component/inline_template.rb +2 -4
  15. data/lib/view_component/instrumentation.rb +5 -11
  16. data/lib/view_component/preview.rb +3 -12
  17. data/lib/view_component/request_details.rb +30 -0
  18. data/lib/view_component/slot.rb +3 -6
  19. data/lib/view_component/slotable.rb +30 -37
  20. data/lib/view_component/system_test_helpers.rb +1 -2
  21. data/lib/view_component/template.rb +106 -83
  22. data/lib/view_component/test_helpers.rb +22 -42
  23. data/lib/view_component/translatable.rb +24 -23
  24. data/lib/view_component/use_helpers.rb +3 -4
  25. data/lib/view_component/version.rb +3 -3
  26. data/lib/view_component.rb +0 -1
  27. metadata +88 -124
  28. data/app/assets/vendor/prism.css +0 -4
  29. data/app/assets/vendor/prism.min.js +0 -12
  30. data/app/helpers/preview_helper.rb +0 -85
  31. data/app/views/view_components/_preview_source.html.erb +0 -17
  32. data/lib/rails/generators/abstract_generator.rb +0 -56
  33. data/lib/rails/generators/component/component_generator.rb +0 -67
  34. data/lib/rails/generators/component/templates/component.rb.tt +0 -16
  35. data/lib/rails/generators/erb/component_generator.rb +0 -33
  36. data/lib/rails/generators/erb/templates/component.html.erb.tt +0 -1
  37. data/lib/rails/generators/haml/component_generator.rb +0 -22
  38. data/lib/rails/generators/haml/templates/component.html.haml.tt +0 -1
  39. data/lib/rails/generators/locale/component_generator.rb +0 -46
  40. data/lib/rails/generators/preview/component_generator.rb +0 -39
  41. data/lib/rails/generators/preview/templates/component_preview.rb.tt +0 -9
  42. data/lib/rails/generators/rspec/component_generator.rb +0 -31
  43. data/lib/rails/generators/rspec/templates/component_spec.rb.tt +0 -15
  44. data/lib/rails/generators/slim/component_generator.rb +0 -22
  45. data/lib/rails/generators/slim/templates/component.html.slim.tt +0 -1
  46. data/lib/rails/generators/stimulus/component_generator.rb +0 -44
  47. data/lib/rails/generators/stimulus/templates/component_controller.js.tt +0 -7
  48. data/lib/rails/generators/stimulus/templates/component_controller.ts.tt +0 -9
  49. data/lib/rails/generators/tailwindcss/component_generator.rb +0 -11
  50. data/lib/rails/generators/tailwindcss/templates/component.html.erb.tt +0 -1
  51. data/lib/rails/generators/test_unit/component_generator.rb +0 -20
  52. data/lib/rails/generators/test_unit/templates/component_test.rb.tt +0 -12
  53. data/lib/view_component/component_error.rb +0 -6
  54. data/lib/view_component/rails/tasks/view_component.rake +0 -20
  55. data/lib/view_component/render_component_helper.rb +0 -10
  56. data/lib/view_component/render_component_to_string_helper.rb +0 -9
  57. data/lib/view_component/render_monkey_patch.rb +0 -13
  58. data/lib/view_component/render_to_string_monkey_patch.rb +0 -13
  59. data/lib/view_component/rendering_component_helper.rb +0 -9
  60. data/lib/view_component/rendering_monkey_patch.rb +0 -13
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 17958e9ab2fbfbce6d2d397332fb0683315d805bb2ca94920a570fcf544c43e8
4
- data.tar.gz: 76b1b5f122715ac0086e78b8010dc5f03e9fad57180ce444c68f556b148d1a59
3
+ metadata.gz: e8c0d5be4a21f5c14ed5447caeaffb8d6e51397a1ec786f573bc58d9486fc747
4
+ data.tar.gz: 332de180e52e1b5eae806fb47e9d1dea4a32374cdf63a0642ca69d3661e4c802
5
5
  SHA512:
6
- metadata.gz: 1ef7030f4690849614463924c9f10953609ef1a74508718cbe5d5f5049256ffcc51c67671a1b95a770bd96c0cd0b3b1f0f438bf56c3ba2265b252d1072f3faad
7
- data.tar.gz: b54825e954ceef4e0f45bbc71cc59f92689275f02da9707e46c3fb30b30eb280b9e01b0b3585b3e6996da92dd5ce2c822d9dd839373e8a89660203a665a0ebb3
6
+ metadata.gz: e298e8e18d147034b30fb60f32a7bbf5d5f42d1235d5947cfeba4e3e1b8ad65a57379f3a3fb361ba7ffdec727687689ceb98b4b893e038a16ea5496663d4423a
7
+ data.tar.gz: f6e8f8956abde8eac1e4825c09a2554dde1dd80efac74ba541210e2fe663e23399ddc01289ca44dee21e6c3551b32117b88444d9795fbb450b02639ebc9fadbf
@@ -10,18 +10,11 @@ module ViewComponent
10
10
  around_action :set_locale, only: :previews
11
11
  before_action :require_local!, unless: :show_previews?
12
12
 
13
- content_security_policy(false) if respond_to?(:content_security_policy)
13
+ content_security_policy(false)
14
14
 
15
15
  # Including helpers here ensures that we're loading the
16
16
  # latest version of helpers if code-reloading is enabled
17
- if include_all_helpers
18
- helper :all
19
- else
20
- # :nocov:
21
- # Always provide the #view_source helper
22
- helper PreviewHelper
23
- # :nocov:
24
- end
17
+ helper :all if include_all_helpers
25
18
  end
26
19
 
27
20
  def index
@@ -1,39 +1,27 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "view_component/errors"
4
-
5
3
  class ViewComponentsSystemTestController < ActionController::Base # :nodoc:
6
- before_action :validate_test_env
7
- before_action :validate_file_path
8
-
9
- rescue_from ViewComponent::SystemTestControllerNefariousPathError, with: :render_not_found
10
-
11
- def self.temp_dir
12
- @_tmpdir ||= FileUtils.mkdir_p("./tmp/view_components/").first
13
- end
14
-
15
- def system_test_entrypoint
16
- render file: @path
17
- end
4
+ if Rails.env.test?
5
+ before_action :validate_file_path
18
6
 
19
- private
7
+ def self.temp_dir
8
+ @_tmpdir ||= FileUtils.mkdir_p("./tmp/view_components/").first
9
+ end
20
10
 
21
- def render_not_found
22
- head :not_found
23
- end
11
+ def system_test_entrypoint
12
+ render file: @path
13
+ end
24
14
 
25
- def validate_test_env
26
- raise ViewComponent::SystemTestControllerOnlyAllowedInTestError unless Rails.env.test?
27
- end
15
+ private
28
16
 
29
- # Ensure that the file path is valid and doesn't target files outside
30
- # the expected directory (e.g. via a path traversal or symlink attack)
31
- def validate_file_path
32
- base_path = ::File.realpath(self.class.temp_dir)
33
- @path = ::File.realpath(params.permit(:file)[:file], base_path)
34
- allowed_prefix = "#{base_path}#{::File::SEPARATOR}"
35
- unless @path == base_path || @path.start_with?(allowed_prefix)
36
- raise ViewComponent::SystemTestControllerNefariousPathError
17
+ # Ensure that the file path is valid and doesn't target files outside
18
+ # the expected directory (e.g. via a path traversal or symlink attack)
19
+ def validate_file_path
20
+ base_path = ::File.realpath(self.class.temp_dir)
21
+ @path = ::File.realpath(params.permit(:file)[:file], base_path)
22
+ unless @path.start_with?(base_path)
23
+ raise ViewComponent::SystemTestControllerNefariousPathError
24
+ end
37
25
  end
38
26
  end
39
27
  end
@@ -0,0 +1 @@
1
+ <%= render AssetComponent.new %>
@@ -1,13 +1,5 @@
1
1
  <% if @render_args[:component] %>
2
- <% if ViewComponent::Base.config.render_monkey_patch_enabled || Rails.version.to_f >= 6.1 %>
3
- <%= render(@render_args[:component], @render_args[:args], &@render_args[:block]) %>
4
- <% else %>
5
- <%= render_component(@render_args[:component], &@render_args[:block]) %>
6
- <% end %>
2
+ <%= render(@render_args[:component], @render_args[:args], &@render_args[:block]) %>
7
3
  <% else %>
8
4
  <%= render template: @render_args[:template], locals: @render_args[:locals] || {} %>
9
5
  <% end %>
10
-
11
- <% if ViewComponent::Base.config.show_previews_source %>
12
- <%= preview_source %>
13
- <% end %>
data/docs/CHANGELOG.md CHANGED
@@ -10,47 +10,107 @@ nav_order: 6
10
10
 
11
11
  ## main
12
12
 
13
- ## 3.25.0
13
+ ## 4.0.0
14
14
 
15
- * Support Rails `render_in` options signature. Rails [#50623](https://github.com/rails/rails/pull/50623) changed the `render_in` signature from `render_in(view_context, &block)` to `render_in(view_context, **options, &block)`. `ViewComponent::Base#render_in`, `ViewComponent::Collection#render_in`, and `ViewComponent::Instrumentation#render_in` now accept `**options`, restoring compatibility with Rails main and silencing the deprecation warning.
15
+ Almost six years after releasing [v1.0.0](https://github.com/ViewComponent/view_component/releases/tag/v1.0.0), we're proud to ship ViewComponent 4. This release marks a shift towards a Long Term Support model for the project, having reached significant feature maturity. While contributions are always welcome, we're unlikely to accept further breaking changes or major feature additions.
16
+
17
+ This release makes the following breaking changes:
18
+
19
+ * BREAKING: `--inline` generator option now generates inline template. Use `--call` to generate `#call` method.
16
20
 
17
21
  *Joel Hawksley*
18
22
 
19
- * Fix stale render context on reused component instances. A `ViewComponent::Base` instance memoized its controller, helpers, request, view context, lookup context, view flow, and requested format/variant on first render via `||=`. Rendering the same instance a second time (intentionally or via aliasing) reused that stale context, which could leak data across requests, sessions, or users. `#render_in` now resets these ivars on every call so each render derives its context from the current view.
23
+ * BREAKING: Remove `use_deprecated_instrumentation_name` configuration option. Events will always use `render.view_component` name.
20
24
 
21
25
  *Joel Hawksley*
22
26
 
23
- * Fix path traversal vulnerability in `ViewComponentsSystemTestController` where sibling directories sharing a string prefix with the allowed temp directory could bypass the path containment check. The `start_with?` check has been replaced with a separator-aware prefix check, and nefarious path errors now return a 404 instead of an unhandled exception.
27
+ * BREAKING: Remove `preview_source` functionality. Consider using [Lookbook](https://lookbook.build/) instead.
24
28
 
25
29
  *Joel Hawksley*
26
30
 
27
- * Fix preview route vulnerability where inherited methods on `ViewComponent::Preview` (such as `render_with_template`) could be invoked via the preview URL, allowing arbitrary internal Rails templates to be rendered with attacker-controlled locals and request parameters. `render_args` now raises `AbstractController::ActionNotFound` for any example not explicitly declared on the preview subclass.
31
+ * BREAKING: Use `Nokogiri::HTML5` instead of `Nokogiri::HTML4` for test helpers.
32
+
33
+ *Noah Silvera*, *Joel Hawksley*
34
+
35
+ * BREAKING: Move generators to a ViewComponent namespace.
36
+
37
+ Before, ViewComponent generators pollute the generator namespace with a bunch of top level items, and claim the generic "component" name.
38
+
39
+ Now, generators live in a "view_component" module/namespace, so what was before `rails g
40
+ component` is now `rails g view_component:component`.
41
+
42
+ *Paul Sadauskas*
43
+
44
+ * BREAKING: Require [non-EOL](https://endoflife.date/rails) Rails (`>= 7.1.0`).
28
45
 
29
46
  *Joel Hawksley*
30
47
 
31
- ## 3.24.0
48
+ * BREAKING: Require [non-EOL](https://www.ruby-lang.org/en/downloads/branches/) Ruby (`>= 3.2.0`).
32
49
 
33
- * Add Rails 8.1 support to ViewComponent v3.
50
+ *Joel Hawksley*
34
51
 
35
- *Hans Lemuet*
52
+ * BREAKING: Remove `render_component` and `render` monkey patch configured with `render_monkey_patch_enabled`.
36
53
 
37
- ## 3.23.2
54
+ *Joel Hawksley*
38
55
 
39
- * Include .tt files in published gem. Fixes templates not being available when using generators.
56
+ * BREAKING: Remove support for variant names containing `.` to be consistent with Rails.
40
57
 
41
- *Florian Aßmann*
58
+ *Stephen Nelson*
42
59
 
43
- ## 3.23.1
60
+ * BREAKING: Use ActionView's `lookup_context` for picking templates instead of the request format.
44
61
 
45
- * Restore Rake tasks in published gem.
62
+ 3.15 added support for using templates that match the request format, i.e. if `/resource.csv` is requested then
63
+ ViewComponents would pick `_component.csv.erb` over `_component.html.erb`.
46
64
 
47
- *Franz Liedke*
65
+ With this release, the request format is no longer considered and instead ViewComponent will use the Rails logic
66
+ for picking the most appropriate template type, i.e. the csv template will be used if it matches the `Accept` header
67
+ or because the controller uses a `respond_to` block to pick the response format.
68
+
69
+ *Stephen Nelson*
70
+
71
+ * BREAKING: Rename internal methods to have `__vc_` prefix if they shouldn't be used by consumers. Make internal constants private. Make `Collection#components`, `Slotable#register_polymorphic_slot` private. Remove unused `ComponentError` class.
72
+
73
+ *Joel Hawksley*
74
+
75
+ * Fix bug where request-aware helpers did not work outside of the request context.
76
+
77
+ *Joel Hawksley*, *Stephen Nelson*
78
+
79
+ * `ViewComponentsSystemTestController` should not be useable outside of test environment
80
+
81
+ *Joel Hawksley*, *Stephen Nelson*
48
82
 
49
- ## 3.23.0
83
+ * Remove unnecessary ENABLE_RELOADING test suite flag.
84
+
85
+ *Joel Hawksley*
86
+
87
+ * Add test coverage for uncovered code.
88
+
89
+ *Joel Hawksley*
90
+
91
+ * Remove unnecessary `#format` methods that returned `nil`.
92
+
93
+ *Joel Hawksley*
94
+
95
+ * Clean up project dependencies, relaxing versions of development gems.
96
+
97
+ *Joel Hawksley*
98
+
99
+ * Test against `turbo-rails` `v2`.
100
+
101
+ *Joel Hawksley*
102
+
103
+ * Test against `rspec-rails` `v7`.
104
+
105
+ *Joel Hawksley*
106
+
107
+ * Remove unnecessary usage of `ruby2_keywords`.
108
+
109
+ *Joel Hawksley*
50
110
 
51
- * Add docs about Slack channel in Ruby Central workspace. (Join us! #oss-view-component). Email joelhawksley@github.com for an invite.
111
+ * Remove unnecessary `respond_to` checks.
52
112
 
53
- *Joel Hawksley
113
+ *Tiago Menegaz*, *Joel Hawksley*
54
114
 
55
115
  * Do not include internal `DocsBuilderComponent` or `YARD::MattrAccessorHandler` in published gem.
56
116
 
@@ -9,6 +9,7 @@ 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
14
  require "view_component/slotable_default"
14
15
  require "view_component/template"
@@ -25,10 +26,9 @@ module ViewComponent
25
26
  #
26
27
  # @return [ActiveSupport::OrderedOptions]
27
28
  def config
28
- module_parents.each do |module_parent|
29
- next unless module_parent.respond_to?(:config)
30
- module_parent_config = module_parent.config.try(:view_component)
31
- return module_parent_config if module_parent_config
29
+ module_parents.each do |m|
30
+ config = m.try(:config).try(:view_component)
31
+ return config if config
32
32
  end
33
33
  ViewComponent::Config.current
34
34
  end
@@ -40,9 +40,6 @@ module ViewComponent
40
40
  include ViewComponent::Translatable
41
41
  include ViewComponent::WithContentHelper
42
42
 
43
- RESERVED_PARAMETER = :content
44
- VC_INTERNAL_DEFAULT_FORMAT = :html
45
-
46
43
  # For CSRF authenticity tokens in forms
47
44
  delegate :form_authenticity_token, :protect_against_forgery?, :config, to: :helpers
48
45
 
@@ -50,8 +47,7 @@ module ViewComponent
50
47
  delegate :content_security_policy_nonce, to: :helpers
51
48
 
52
49
  # 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
50
+ class_attribute :__vc_strip_trailing_whitespace, instance_accessor: false, instance_predicate: false, default: false
55
51
 
56
52
  attr_accessor :__vc_original_view_context
57
53
 
@@ -65,54 +61,41 @@ module ViewComponent
65
61
  # @param view_context [ActionView::Base] The original view context.
66
62
  # @return [void]
67
63
  def set_original_view_context(view_context)
68
- # Stash on a separate ivar that survives `__vc_reset_render_state!` so a
69
- # parent component can call `set_original_view_context` immediately before
70
- # `render_in` without having that value clobbered by the per-render reset.
71
- @__vc_pending_original_view_context = view_context
64
+ self.__vc_original_view_context = view_context
72
65
  end
73
66
 
67
+ using RequestDetails
68
+
74
69
  # Entrypoint for rendering components.
75
70
  #
76
71
  # - `view_context`: ActionView context from calling view
77
- # - `options`: optional render options (e.g., locals)
78
72
  # - `block`: optional block to be captured within the view context
79
73
  #
80
74
  # Returns HTML that has been escaped by the respective template handler.
81
75
  #
82
76
  # @return [String]
83
- def render_in(view_context, **options, &block)
84
- self.class.compile(raise_errors: true)
85
-
86
- __vc_reset_render_state!
77
+ def render_in(view_context, &block)
78
+ self.class.__vc_compile(raise_errors: true)
87
79
 
88
80
  @view_context = view_context
89
- self.__vc_original_view_context =
90
- if instance_variable_defined?(:@__vc_pending_original_view_context)
91
- remove_instance_variable(:@__vc_pending_original_view_context)
92
- else
93
- view_context
94
- end
81
+ self.__vc_original_view_context ||= view_context
95
82
 
96
83
  @output_buffer = ActionView::OutputBuffer.new
97
84
 
98
- @lookup_context = view_context.lookup_context
99
-
100
- # required for path helpers in older Rails versions
101
- @view_renderer = view_context.view_renderer
85
+ @lookup_context ||= view_context.lookup_context
102
86
 
103
87
  # For content_for
104
- @view_flow = view_context.view_flow
88
+ @view_flow ||= view_context.view_flow
105
89
 
106
90
  # For i18n
107
91
  @virtual_path ||= virtual_path
108
92
 
109
- # For template variants (+phone, +desktop, etc.)
110
- @__vc_variant = @lookup_context.variants.first
93
+ # Describes the inferred request constraints (locales, formats, variants)
94
+ @__vc_requested_details ||= @lookup_context.vc_requested_details
111
95
 
112
96
  # For caching, such as #cache_if
113
97
  @current_template = nil unless defined?(@current_template)
114
98
  old_current_template = @current_template
115
- @current_template = self
116
99
 
117
100
  if block && defined?(@__vc_content_set_by_with_content)
118
101
  raise DuplicateContentError.new(self.class.name)
@@ -124,7 +107,7 @@ module ViewComponent
124
107
  before_render
125
108
 
126
109
  if render?
127
- rendered_template = render_template_for(@__vc_variant, __vc_request&.format&.to_sym).to_s
110
+ rendered_template = render_template_for(@__vc_requested_details).to_s
128
111
 
129
112
  # Avoid allocating new string when output_preamble and output_postamble are blank
130
113
  if output_preamble.blank? && output_postamble.blank?
@@ -172,7 +155,7 @@ module ViewComponent
172
155
  target_render = self.class.instance_variable_get(:@__vc_ancestor_calls)[@__vc_parent_render_level]
173
156
  @__vc_parent_render_level += 1
174
157
 
175
- target_render.bind_call(self, @__vc_variant)
158
+ target_render.bind_call(self, @__vc_requested_details)
176
159
  ensure
177
160
  @__vc_parent_render_level -= 1
178
161
  end
@@ -217,12 +200,12 @@ module ViewComponent
217
200
  #
218
201
  # This prevents an exception when rendering a partial inside of a component that has also been rendered outside
219
202
  # of the component. This is due to the partials compiled template method existing in the parent `view_context`,
220
- # and not the component's `view_context`.
203
+ # and not the component's `view_context`.
221
204
  #
222
205
  # @private
223
206
  def render(options = {}, args = {}, &block)
224
207
  if options.respond_to?(:set_original_view_context)
225
- options.set_original_view_context(__vc_original_view_context)
208
+ options.set_original_view_context(self.__vc_original_view_context)
226
209
  super
227
210
  else
228
211
  __vc_original_view_context.render(options, args, &block)
@@ -265,7 +248,7 @@ module ViewComponent
265
248
  raise e, <<~MESSAGE.chomp if view_context && e.is_a?(NameError) && helpers.respond_to?(method_name)
266
249
  #{e.message}
267
250
 
268
- You may be trying to call a method provided as a view helper. Did you mean `helpers.#{method_name}`?
251
+ You may be trying to call a method provided as a view helper. Did you mean `helpers.#{method_name}'?
269
252
  MESSAGE
270
253
 
271
254
  raise
@@ -285,13 +268,6 @@ module ViewComponent
285
268
  []
286
269
  end
287
270
 
288
- # For caching, such as #cache_if
289
- #
290
- # @private
291
- def format
292
- @__vc_variant if defined?(@__vc_variant)
293
- end
294
-
295
271
  # The current request. Use sparingly as doing so introduces coupling that
296
272
  # inhibits encapsulation & reuse, often making testing difficult.
297
273
  #
@@ -300,10 +276,9 @@ module ViewComponent
300
276
  __vc_request
301
277
  end
302
278
 
303
- # Enables consumers to override request/@request
304
- #
305
279
  # @private
306
280
  def __vc_request
281
+ # The current request (if present, as mailers/jobs/etc do not have a request)
307
282
  @__vc_request ||= controller.request if controller.respond_to?(:request)
308
283
  end
309
284
 
@@ -346,7 +321,7 @@ module ViewComponent
346
321
  end
347
322
 
348
323
  def maybe_escape_html(text)
349
- return text if __vc_request && !__vc_request.format.html?
324
+ return text if @current_template && !@current_template.html?
350
325
  return text if text.blank?
351
326
 
352
327
  if text.html_safe?
@@ -379,13 +354,6 @@ module ViewComponent
379
354
  # configured on a per-test basis using `with_controller_class`.
380
355
  #
381
356
 
382
- # Set if render monkey patches should be included or not in Rails <6.1:
383
- #
384
- # ```ruby
385
- # config.view_component.render_monkey_patch_enabled = false
386
- # ```
387
- #
388
-
389
357
  # Path for component files
390
358
  #
391
359
  # ```ruby
@@ -405,23 +373,6 @@ module ViewComponent
405
373
  # "ApplicationComponent" if defined, "ViewComponent::Base" otherwise.
406
374
  #
407
375
 
408
- # Resets every render-scoped instance variable derived from the calling view
409
- # context so a reused instance cannot leak controller/helper/request/format
410
- # state from a previous render. Slot state (`@__vc_set_slots`,
411
- # `@__vc_content_set_by_with_content`) is intentionally preserved because it
412
- # is populated by callers _before_ `render_in` runs (e.g. via `with_*`
413
- # slot setters or `with_content`).
414
- def __vc_reset_render_state!
415
- %i[
416
- @__vc_controller
417
- @__vc_helpers
418
- @__vc_request
419
- @__vc_original_view_context
420
- ].each do |ivar|
421
- remove_instance_variable(ivar) if instance_variable_defined?(ivar)
422
- end
423
- end
424
-
425
376
  # Configuration for generators.
426
377
  #
427
378
  # All options under this namespace default to `false` unless otherwise
@@ -551,20 +502,20 @@ module ViewComponent
551
502
  def inherited(child)
552
503
  # Compile so child will inherit compiled `call_*` template methods that
553
504
  # `compile` defines
554
- compile
505
+ __vc_compile
555
506
 
556
507
  # Give the child its own personal #render_template_for to protect against the case when
557
508
  # eager loading is disabled and the parent component is rendered before the child. In
558
509
  # such a scenario, the parent will override ViewComponent::Base#render_template_for,
559
510
  # meaning it will not be called for any children and thus not compile their templates.
560
- if !child.instance_methods(false).include?(:render_template_for) && !child.compiled?
511
+ if !child.instance_methods(false).include?(:render_template_for) && !child.__vc_compiled?
561
512
  child.class_eval <<~RUBY, __FILE__, __LINE__ + 1
562
- def render_template_for(variant = nil, format = nil)
513
+ def render_template_for(requested_details)
563
514
  # Force compilation here so the compiler always redefines render_template_for.
564
515
  # This is mostly a safeguard to prevent infinite recursion.
565
- self.class.compile(raise_errors: true, force: true)
566
- # .compile replaces this method; call the new one
567
- render_template_for(variant, format)
516
+ self.class.__vc_compile(raise_errors: true, force: true)
517
+ # .__vc_compile replaces this method; call the new one
518
+ render_template_for(requested_details)
568
519
  end
569
520
  RUBY
570
521
  end
@@ -603,22 +554,22 @@ module ViewComponent
603
554
  end
604
555
 
605
556
  # @private
606
- def compiled?
607
- compiler.compiled?
557
+ def __vc_compiled?
558
+ __vc_compiler.compiled?
608
559
  end
609
560
 
610
561
  # @private
611
- def ensure_compiled
612
- compile unless compiled?
562
+ def __vc_ensure_compiled
563
+ __vc_compile unless __vc_compiled?
613
564
  end
614
565
 
615
566
  # @private
616
- def compile(raise_errors: false, force: false)
617
- compiler.compile(raise_errors: raise_errors, force: force)
567
+ def __vc_compile(raise_errors: false, force: false)
568
+ __vc_compiler.compile(raise_errors: raise_errors, force: force)
618
569
  end
619
570
 
620
571
  # @private
621
- def compiler
572
+ def __vc_compiler
622
573
  @__vc_compiler ||= Compiler.new(self)
623
574
  end
624
575
 
@@ -660,8 +611,8 @@ module ViewComponent
660
611
  # is accepted, as support for collection
661
612
  # rendering is optional.
662
613
  # @private
663
- def validate_collection_parameter!(validate_default: false)
664
- parameter = validate_default ? collection_parameter : provided_collection_parameter
614
+ def __vc_validate_collection_parameter!(validate_default: false)
615
+ parameter = validate_default ? __vc_collection_parameter : provided_collection_parameter
665
616
 
666
617
  return unless parameter
667
618
  return if initialize_parameter_names.include?(parameter) || splatted_keyword_argument_present?
@@ -680,35 +631,35 @@ module ViewComponent
680
631
  # invalid parameters that could override the framework's
681
632
  # methods.
682
633
  # @private
683
- def validate_initialization_parameters!
684
- return unless initialize_parameter_names.include?(RESERVED_PARAMETER)
634
+ def __vc_validate_initialization_parameters!
635
+ return unless initialize_parameter_names.include?(:content)
685
636
 
686
- raise ReservedParameterError.new(name, RESERVED_PARAMETER)
637
+ raise ReservedParameterError.new(name, :content)
687
638
  end
688
639
 
689
640
  # @private
690
- def collection_parameter
641
+ def __vc_collection_parameter
691
642
  provided_collection_parameter || name && name.demodulize.underscore.chomp("_component").to_sym
692
643
  end
693
644
 
694
645
  # @private
695
- def collection_counter_parameter
696
- :"#{collection_parameter}_counter"
646
+ def __vc_collection_counter_parameter
647
+ :"#{__vc_collection_parameter}_counter"
697
648
  end
698
649
 
699
650
  # @private
700
- def counter_argument_present?
701
- initialize_parameter_names.include?(collection_counter_parameter)
651
+ def __vc_counter_argument_present?
652
+ initialize_parameter_names.include?(__vc_collection_counter_parameter)
702
653
  end
703
654
 
704
655
  # @private
705
- def collection_iteration_parameter
706
- :"#{collection_parameter}_iteration"
656
+ def __vc_collection_iteration_parameter
657
+ :"#{__vc_collection_parameter}_iteration"
707
658
  end
708
659
 
709
660
  # @private
710
- def iteration_argument_present?
711
- initialize_parameter_names.include?(collection_iteration_parameter)
661
+ def __vc_iteration_argument_present?
662
+ initialize_parameter_names.include?(__vc_collection_iteration_parameter)
712
663
  end
713
664
 
714
665
  private
@@ -721,8 +672,6 @@ module ViewComponent
721
672
  def initialize_parameter_names
722
673
  return attribute_names.map(&:to_sym) if respond_to?(:attribute_names)
723
674
 
724
- return attribute_types.keys.map(&:to_sym) if Rails::VERSION::MAJOR <= 5 && respond_to?(:attribute_types)
725
-
726
675
  initialize_parameters.map(&:last)
727
676
  end
728
677
 
@@ -1,11 +1,10 @@
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
7
7
  include Enumerable
8
-
9
8
  attr_reader :component
10
9
 
11
10
  delegate :size, to: :@collection
@@ -16,10 +15,10 @@ module ViewComponent
16
15
  self.__vc_original_view_context = view_context
17
16
  end
18
17
 
19
- def render_in(view_context, **options, &block)
18
+ def render_in(view_context, &block)
20
19
  components.map do |component|
21
20
  component.set_original_view_context(__vc_original_view_context)
22
- component.render_in(view_context, **options, &block)
21
+ component.render_in(view_context, &block)
23
22
  end.join(rendered_spacer(view_context)).html_safe
24
23
  end
25
24
 
@@ -27,23 +26,17 @@ module ViewComponent
27
26
  components.each(&block)
28
27
  end
29
28
 
30
- # Rails expects us to define `format` on all renderables,
31
- # but we do not know the `format` of a ViewComponent until runtime.
32
- def format
33
- nil
34
- end
35
-
36
29
  private
37
30
 
38
- # Always rebuild child component instances per render to avoid leaking
39
- # request-scoped state from a previous render into a later one.
40
31
  def components
32
+ return @components if defined? @components
33
+
41
34
  iterator = ActionView::PartialIteration.new(@collection.size)
42
35
 
43
- component.validate_collection_parameter!(validate_default: true)
36
+ component.__vc_validate_collection_parameter!(validate_default: true)
44
37
 
45
- @collection.map do |item|
46
- component.new(**component_options(item, iterator)).tap do |_|
38
+ @components = @collection.map do |item|
39
+ component.new(**component_options(item, iterator)).tap do |component|
47
40
  iterator.iterate!
48
41
  end
49
42
  end
@@ -65,16 +58,16 @@ module ViewComponent
65
58
  end
66
59
 
67
60
  def component_options(item, iterator)
68
- item_options = {component.collection_parameter => item}
69
- item_options[component.collection_counter_parameter] = iterator.index if component.counter_argument_present?
70
- item_options[component.collection_iteration_parameter] = iterator.dup if component.iteration_argument_present?
61
+ item_options = {component.__vc_collection_parameter => item}
62
+ item_options[component.__vc_collection_counter_parameter] = iterator.index if component.__vc_counter_argument_present?
63
+ item_options[component.__vc_collection_iteration_parameter] = iterator.dup if component.__vc_iteration_argument_present?
71
64
 
72
65
  @options.merge(item_options)
73
66
  end
74
67
 
75
68
  def rendered_spacer(view_context)
76
69
  if @spacer_component
77
- @spacer_component.set_original_view_context(__vc_original_view_context) if @spacer_component.respond_to?(:set_original_view_context)
70
+ @spacer_component.set_original_view_context(__vc_original_view_context)
78
71
  @spacer_component.render_in(view_context)
79
72
  else
80
73
  ""