-
Notifications
You must be signed in to change notification settings - Fork 485
Refactor compiler to use ActionView's partial sorting algorithm #2158
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -28,6 +28,17 @@ | |||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| *Stephen Nelson* | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| * BREAKING: Use ActionView's `lookup_context` for picking templates instead of the request format. | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| 3.15 added support for using templates that match the request format, i.e. if `/resource.csv` is requested then | ||||||||||||||||||||||||||||||||||||||
|
Check failure on line 33 in docs/CHANGELOG.md
|
||||||||||||||||||||||||||||||||||||||
| ViewComponents would pick `_component.csv.erb` over `_component.html.erb`. | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| With this release, the request format is no longer considered and instead ViewComponent will use the Rails logic | ||||||||||||||||||||||||||||||||||||||
| for picking the most appropriate template type, i.e. the csv template will be used if it matches the `Accept` header | ||||||||||||||||||||||||||||||||||||||
|
Check failure on line 37 in docs/CHANGELOG.md
|
||||||||||||||||||||||||||||||||||||||
| or because the controller uses a `respond_to` block to pick the response format. | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| *Stephen Nelson* | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+31
to
+41
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||||||||||||||||||||||
| * Ensure HTML output safety wrapper is used for all inline templates. | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| *Joel Hawksley* | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -9,6 +9,7 @@ | |
| require "view_component/errors" | ||
| require "view_component/inline_template" | ||
| require "view_component/preview" | ||
| require "view_component/request_details" | ||
| require "view_component/slotable" | ||
| require "view_component/slotable_default" | ||
| require "view_component/template" | ||
|
|
@@ -63,6 +64,8 @@ def set_original_view_context(view_context) | |
| self.__vc_original_view_context = view_context | ||
| end | ||
|
|
||
| using RequestDetails | ||
|
|
||
| # Entrypoint for rendering components. | ||
| # | ||
| # - `view_context`: ActionView context from calling view | ||
|
|
@@ -90,13 +93,12 @@ def render_in(view_context, &block) | |
| # For i18n | ||
| @virtual_path ||= virtual_path | ||
|
|
||
| # For template variants (+phone, +desktop, etc.) | ||
| @__vc_variant ||= @lookup_context.variants.first | ||
| # Describes the inferred request constraints (locales, formats, variants) | ||
| @__vc_requested_details ||= @lookup_context.vc_requested_details | ||
|
|
||
| # For caching, such as #cache_if | ||
| @current_template = nil unless defined?(@current_template) | ||
| old_current_template = @current_template | ||
| @current_template = self | ||
|
|
||
| if block && defined?(@__vc_content_set_by_with_content) | ||
| raise DuplicateContentError.new(self.class.name) | ||
|
|
@@ -108,7 +110,7 @@ def render_in(view_context, &block) | |
| before_render | ||
|
|
||
| if render? | ||
| rendered_template = render_template_for(@__vc_variant, __vc_request&.format&.to_sym).to_s | ||
| rendered_template = render_template_for(@__vc_requested_details).to_s | ||
|
|
||
| # Avoid allocating new string when output_preamble and output_postamble are blank | ||
| if output_preamble.blank? && output_postamble.blank? | ||
|
|
@@ -156,7 +158,7 @@ def render_parent_to_string | |
| target_render = self.class.instance_variable_get(:@__vc_ancestor_calls)[@__vc_parent_render_level] | ||
| @__vc_parent_render_level += 1 | ||
|
|
||
| target_render.bind_call(self, @__vc_variant) | ||
| target_render.bind_call(self, @__vc_requested_details) | ||
| ensure | ||
| @__vc_parent_render_level -= 1 | ||
| end | ||
|
|
@@ -267,11 +269,10 @@ def view_cache_dependencies | |
| [] | ||
| end | ||
|
|
||
| # For caching, such as #cache_if | ||
| # | ||
| # @private | ||
| # Rails expects us to define `format` on all renderables, | ||
| # but we do not know the `format` of a ViewComponent until runtime. | ||
| def format | ||
| @__vc_variant if defined?(@__vc_variant) | ||
| nil | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I've spent some time investigating why this code returned the variant and I still don't really understand why. It was added during 1.x and it's been refactored numerous times. I don't think this has any meaningful contribution to caching anymore as Rails cache already uses the
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If that's the case, then let's just remove this method entirely. It's optional anyways: https://github.com/rails/rails/blob/main/actionview/lib/action_view/template/renderable.rb#L26 |
||
| end | ||
|
|
||
| # The current request. Use sparingly as doing so introduces coupling that | ||
|
|
@@ -328,7 +329,7 @@ def content_evaluated? | |
| end | ||
|
|
||
| def maybe_escape_html(text) | ||
| return text if __vc_request && !__vc_request.format.html? | ||
| return text if @current_template && !@current_template.html? | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Note that current_template is set by |
||
| return text if text.blank? | ||
|
|
||
| if text.html_safe? | ||
|
|
@@ -517,12 +518,12 @@ def inherited(child) | |
| # meaning it will not be called for any children and thus not compile their templates. | ||
| if !child.instance_methods(false).include?(:render_template_for) && !child.compiled? | ||
| child.class_eval <<~RUBY, __FILE__, __LINE__ + 1 | ||
| def render_template_for(variant = nil, format = nil) | ||
| def render_template_for(requested_details) | ||
| # Force compilation here so the compiler always redefines render_template_for. | ||
| # This is mostly a safeguard to prevent infinite recursion. | ||
| self.class.compile(raise_errors: true, force: true) | ||
| # .compile replaces this method; call the new one | ||
| render_template_for(variant, format) | ||
| render_template_for(requested_details) | ||
| end | ||
| RUBY | ||
| end | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| # frozen_string_literal: true | ||
|
|
||
| module ViewComponent | ||
| # LookupContext computes and encapsulates @details for each request | ||
| # so that it doesn't need to be recomputed on each partial render. | ||
| # This data is wrapped in ActionView::TemplateDetails::Requested and | ||
| # used by instances of ActionView::Resolver to choose which template | ||
| # best matches the request. | ||
| # | ||
| # ActionView considers this logic internal to template/partial resolution. | ||
| # We're exposing it to the compiler via `refine` so that ViewComponent | ||
| # can match Rails' template picking logic. | ||
| module RequestDetails | ||
| refine ActionView::LookupContext do | ||
| # Return an abstraction for matching and sorting available templates | ||
| # based on the current lookup context details. | ||
| # | ||
| # @return ActionView::TemplateDetails::Requested | ||
| # @see ActionView::LookupContext#detail_args_for | ||
| # @see ActionView::FileSystemResolver#_find_all | ||
| def vc_requested_details(user_details = {}) | ||
| # The hash `user_details` would normally be the standard arguments that | ||
| # `render` accepts, but there's currently no mechanism for users to | ||
| # provide these when calling render on a ViewComponent. | ||
| details, cached = detail_args_for(user_details) | ||
| cached || ActionView::TemplateDetails::Requested.new(**details) | ||
| end | ||
| end | ||
| end | ||
| end |
Uh oh!
There was an error while loading. Please reload this page.