From e2af193fa8c7c66b3fcedeb82dd4b8426ae43856 Mon Sep 17 00:00:00 2001 From: Reegan Viljoen Date: Tue, 8 Oct 2024 21:53:34 +0200 Subject: [PATCH 01/70] add component controlled cache --- .tool-versions | 2 +- lib/view_component/base.rb | 5 ++++ lib/view_component/cache_on.rb | 27 +++++++++++++++++++ .../app/components/cache_component.html.erb | 2 ++ .../sandbox/app/components/cache_component.rb | 12 +++++++++ test/sandbox/test/rendering_test.rb | 17 ++++++++++++ 6 files changed, 64 insertions(+), 1 deletion(-) create mode 100644 lib/view_component/cache_on.rb create mode 100644 test/sandbox/app/components/cache_component.html.erb create mode 100644 test/sandbox/app/components/cache_component.rb diff --git a/.tool-versions b/.tool-versions index a72ead61f..ae5ecdb2b 100644 --- a/.tool-versions +++ b/.tool-versions @@ -1 +1 @@ -ruby 3.4.3 +ruby 3.4.2 diff --git a/lib/view_component/base.rb b/lib/view_component/base.rb index 51bc9619f..402ef670c 100644 --- a/lib/view_component/base.rb +++ b/lib/view_component/base.rb @@ -14,6 +14,7 @@ require "view_component/template" require "view_component/translatable" require "view_component/with_content_helper" +require "view_component/cache_on" module ActionView class OutputBuffer @@ -55,6 +56,10 @@ def config include ViewComponent::Slotable include ViewComponent::Translatable include ViewComponent::WithContentHelper + include ViewComponent::CacheOn + + RESERVED_PARAMETER = :content + VC_INTERNAL_DEFAULT_FORMAT = :html # For CSRF authenticity tokens in forms delegate :form_authenticity_token, :protect_against_forgery?, :config, to: :helpers diff --git a/lib/view_component/cache_on.rb b/lib/view_component/cache_on.rb new file mode 100644 index 000000000..938bc9355 --- /dev/null +++ b/lib/view_component/cache_on.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +module ViewComponent::CacheOn + extend ActiveSupport::Concern + + included do + def cache_key + @vc_cache_args = vc_cache_args.map { |method| send(method) } if defined?(vc_cache_args) + + @vc_cache_key = Digest::MD5.hexdigest(@vc_cache_args.join) + end + end + + class_methods do + def cache_on(*args) + define_method(:vc_cache_args) { args } + end + + def call + if cache_key + Rails.cache.fetch(cache_key) { super } + else + super + end + end + end +end diff --git a/test/sandbox/app/components/cache_component.html.erb b/test/sandbox/app/components/cache_component.html.erb new file mode 100644 index 000000000..c58061f24 --- /dev/null +++ b/test/sandbox/app/components/cache_component.html.erb @@ -0,0 +1,2 @@ +

<%= cache_key %>

+

<%= "#{foo} #{bar}" %>

diff --git a/test/sandbox/app/components/cache_component.rb b/test/sandbox/app/components/cache_component.rb new file mode 100644 index 000000000..77fd73587 --- /dev/null +++ b/test/sandbox/app/components/cache_component.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +class CacheComponent < ViewComponent::Base + cache_on :foo, :bar + + attr_reader :foo, :bar + + def initialize(foo:, bar:) + @foo = foo + @bar = bar + end +end diff --git a/test/sandbox/test/rendering_test.rb b/test/sandbox/test/rendering_test.rb index fb456f290..3864dc2f0 100644 --- a/test/sandbox/test/rendering_test.rb +++ b/test/sandbox/test/rendering_test.rb @@ -1321,4 +1321,21 @@ def test_around_render assert_text("Hi!") end + + def test_cache_component + component = CacheComponent.new(foo: "foo", bar: "bar") + render_inline(component) + + assert_selector(".cache-component__cache-key", text: component.cache_key) + assert_selector(".cache-component__cache-message", text: "foo bar") + + render_inline(CacheComponent.new(foo: "foo", bar: "bar")) + + assert_selector(".cache-component__cache-key", text: component.cache_key) + + render_inline(CacheComponent.new(foo: "foo", bar: "baz")) + + refute_selector(".cache-component__cache-key", text: component.cache_key) + refute_selector(".cache-component__cache-message", text: "foo bar") + end end From d71dc5fb48687b17bdddbb4a55a7c994d0fd97ac Mon Sep 17 00:00:00 2001 From: Reegan Viljoen Date: Tue, 8 Oct 2024 22:02:54 +0200 Subject: [PATCH 02/70] add changelog --- docs/CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 3e7193c7e..42181a3a5 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -457,6 +457,10 @@ This release makes the following breaking changes: *Javier Aranda* +* Add first class component cache. + + *Reegan Viljoen* + ## 3.17.0 * Use struct instead openstruct in lib code. From e7f73970da2c08dc5262cb9e966c90b0c4ca1dc2 Mon Sep 17 00:00:00 2001 From: Reegan Viljoen Date: Tue, 15 Oct 2024 21:27:10 +0200 Subject: [PATCH 03/70] fix cacahe implementatation to work with all methods --- lib/view_component/base.rb | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/lib/view_component/base.rb b/lib/view_component/base.rb index 402ef670c..74afeb353 100644 --- a/lib/view_component/base.rb +++ b/lib/view_component/base.rb @@ -81,6 +81,19 @@ def config attr_accessor :__vc_original_view_context attr_reader :current_template + # TODO + # + # @return [String] + def cache_key + @vc_cache_key = if defined?(__vc_cache_args) + Digest::MD5.hexdigest( + __vc_cache_args.map { |method| send(method) }.join("-") + ) + else + nil + end + end + # Components render in their own view context. Helpers and other functionality # require a reference to the original Rails view context, an instance of # `ActionView::Base`. Use this method to set a reference to the original @@ -371,6 +384,16 @@ def __vc_render_in_block_provided? defined?(@view_context) && @view_context && @__vc_render_in_block end + # TODO + def __vc_render_template(rendered_template) + # Avoid allocating new string when output_preamble and output_postamble are blank + if output_preamble.blank? && output_postamble.blank? + rendered_template + else + safe_output_preamble + rendered_template + safe_output_postamble + end + end + def __vc_content_set_by_with_content_defined? defined?(@__vc_content_set_by_with_content) end @@ -523,6 +546,14 @@ def sidecar_files(extensions) (sidecar_files - [identifier] + sidecar_directory_files + nested_component_files).uniq end + def cache_on(*args) + class_eval <<~RUBY, __FILE__, __LINE__ + 1 + def __vc_cache_args + #{args} + end + RUBY + end + # Render a component for each element in a collection ([documentation](/guide/collections)): # # ```ruby From 9a21b4cc52349b613c268f834a3693c07f258a22 Mon Sep 17 00:00:00 2001 From: Reegan Viljoen Date: Tue, 15 Oct 2024 21:28:07 +0200 Subject: [PATCH 04/70] fix cacahe implementatation to work with all methods --- lib/view_component/base.rb | 5 +---- lib/view_component/cache_on.rb | 27 --------------------------- 2 files changed, 1 insertion(+), 31 deletions(-) delete mode 100644 lib/view_component/cache_on.rb diff --git a/lib/view_component/base.rb b/lib/view_component/base.rb index 74afeb353..0770e3100 100644 --- a/lib/view_component/base.rb +++ b/lib/view_component/base.rb @@ -14,7 +14,6 @@ require "view_component/template" require "view_component/translatable" require "view_component/with_content_helper" -require "view_component/cache_on" module ActionView class OutputBuffer @@ -56,7 +55,6 @@ def config include ViewComponent::Slotable include ViewComponent::Translatable include ViewComponent::WithContentHelper - include ViewComponent::CacheOn RESERVED_PARAMETER = :content VC_INTERNAL_DEFAULT_FORMAT = :html @@ -81,7 +79,7 @@ def config attr_accessor :__vc_original_view_context attr_reader :current_template - # TODO + # Compoents can have a cache key that is used to cache the rendered output. # # @return [String] def cache_key @@ -384,7 +382,6 @@ def __vc_render_in_block_provided? defined?(@view_context) && @view_context && @__vc_render_in_block end - # TODO def __vc_render_template(rendered_template) # Avoid allocating new string when output_preamble and output_postamble are blank if output_preamble.blank? && output_postamble.blank? diff --git a/lib/view_component/cache_on.rb b/lib/view_component/cache_on.rb deleted file mode 100644 index 938bc9355..000000000 --- a/lib/view_component/cache_on.rb +++ /dev/null @@ -1,27 +0,0 @@ -# frozen_string_literal: true - -module ViewComponent::CacheOn - extend ActiveSupport::Concern - - included do - def cache_key - @vc_cache_args = vc_cache_args.map { |method| send(method) } if defined?(vc_cache_args) - - @vc_cache_key = Digest::MD5.hexdigest(@vc_cache_args.join) - end - end - - class_methods do - def cache_on(*args) - define_method(:vc_cache_args) { args } - end - - def call - if cache_key - Rails.cache.fetch(cache_key) { super } - else - super - end - end - end -end From 6d2462ea0d3206f303b722a89fb34b921d06f346 Mon Sep 17 00:00:00 2001 From: Reegan Viljoen Date: Tue, 15 Oct 2024 21:30:44 +0200 Subject: [PATCH 05/70] fix lint --- lib/view_component/base.rb | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/view_component/base.rb b/lib/view_component/base.rb index 0770e3100..1d4a8acc1 100644 --- a/lib/view_component/base.rb +++ b/lib/view_component/base.rb @@ -87,8 +87,6 @@ def cache_key Digest::MD5.hexdigest( __vc_cache_args.map { |method| send(method) }.join("-") ) - else - nil end end From a8073b7568dae2edba558b8bdca028042d4822fa Mon Sep 17 00:00:00 2001 From: Reegan Viljoen Date: Wed, 16 Oct 2024 22:51:13 +0200 Subject: [PATCH 06/70] yeah I know it aint working, I am tired however, taking a nother look tomorow --- lib/view_component/base.rb | 38 ++++++++++--------- .../app/components/cache_component.html.erb | 2 +- test/sandbox/test/rendering_test.rb | 6 +-- 3 files changed, 24 insertions(+), 22 deletions(-) diff --git a/lib/view_component/base.rb b/lib/view_component/base.rb index 1d4a8acc1..865964eee 100644 --- a/lib/view_component/base.rb +++ b/lib/view_component/base.rb @@ -72,24 +72,15 @@ def config delegate :content_security_policy_nonce, to: :helpers # Config option that strips trailing whitespace in templates before compiling them. - class_attribute :__vc_strip_trailing_whitespace, instance_accessor: false, instance_predicate: false, default: false class_attribute :__vc_response_format, instance_accessor: false, instance_predicate: false, default: nil + class_attribute :__vc_cache_dependencies, instance_accessor: false, instance_predicate: false, default: [] + class_attribute :__vc_strip_trailing_whitespace, instance_accessor: false, instance_predicate: false + self.__vc_strip_trailing_whitespace = false # class_attribute:default doesn't work until Rails 5.2 attr_accessor :__vc_original_view_context attr_reader :current_template - # Compoents can have a cache key that is used to cache the rendered output. - # - # @return [String] - def cache_key - @vc_cache_key = if defined?(__vc_cache_args) - Digest::MD5.hexdigest( - __vc_cache_args.map { |method| send(method) }.join("-") - ) - end - end - # Components render in their own view context. Helpers and other functionality # require a reference to the original Rails view context, an instance of # `ActionView::Base`. Use this method to set a reference to the original @@ -329,7 +320,16 @@ def virtual_path # For caching, such as #cache_if # @private def view_cache_dependencies - [] + self.class.view_cache_dependencies + end + + alias_method :component_cache_dependencies, :view_cache_dependencies + + # For caching, such as #cache_if + # + # @private + def format + @__vc_variant if defined?(@__vc_variant) end # The current request. Use sparingly as doing so introduces coupling that @@ -542,11 +542,13 @@ def sidecar_files(extensions) end def cache_on(*args) - class_eval <<~RUBY, __FILE__, __LINE__ + 1 - def __vc_cache_args - #{args} - end - RUBY + __vc_cache_dependencies.push(*args) + end + + def view_cache_dependencies + return unless __vc_cache_dependencies.any? + + __vc_cache_dependencies.filter_map { |dep| send(dep) } end # Render a component for each element in a collection ([documentation](/guide/collections)): diff --git a/test/sandbox/app/components/cache_component.html.erb b/test/sandbox/app/components/cache_component.html.erb index c58061f24..802208680 100644 --- a/test/sandbox/app/components/cache_component.html.erb +++ b/test/sandbox/app/components/cache_component.html.erb @@ -1,2 +1,2 @@ -

<%= cache_key %>

+

<%= view_cache_dependencies %>

<%= "#{foo} #{bar}" %>

diff --git a/test/sandbox/test/rendering_test.rb b/test/sandbox/test/rendering_test.rb index 3864dc2f0..8925a965a 100644 --- a/test/sandbox/test/rendering_test.rb +++ b/test/sandbox/test/rendering_test.rb @@ -1326,16 +1326,16 @@ def test_cache_component component = CacheComponent.new(foo: "foo", bar: "bar") render_inline(component) - assert_selector(".cache-component__cache-key", text: component.cache_key) + assert_selector(".cache-component__cache-key", text: component.component_cache_dependencies) assert_selector(".cache-component__cache-message", text: "foo bar") render_inline(CacheComponent.new(foo: "foo", bar: "bar")) - assert_selector(".cache-component__cache-key", text: component.cache_key) + assert_selector(".cache-component__cache-key", text: component.component_cache_dependencies) render_inline(CacheComponent.new(foo: "foo", bar: "baz")) - refute_selector(".cache-component__cache-key", text: component.cache_key) + refute_selector(".cache-component__cache-key", text: component.component_cache_dependencies) refute_selector(".cache-component__cache-message", text: "foo bar") end end From 7163934a932cbe1285534f6f9677173f9d379b76 Mon Sep 17 00:00:00 2001 From: Reegan Viljoen Date: Tue, 5 Nov 2024 10:46:27 +0200 Subject: [PATCH 07/70] fix cache --- lib/view_component/base.rb | 8 ++++++++ test/sandbox/test/rendering_test.rb | 6 +++--- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/lib/view_component/base.rb b/lib/view_component/base.rb index 865964eee..5144bf736 100644 --- a/lib/view_component/base.rb +++ b/lib/view_component/base.rb @@ -346,6 +346,12 @@ def __vc_request @__vc_request ||= controller.request if controller.respond_to?(:request) end + def view_cache_dependencies + return unless __vc_cache_dependencies.present? && __vc_cache_dependencies.any? + + __vc_cache_dependencies.filter_map { |dep| send(dep) } + end + # The content passed to the component instance as a block. # # @return [String] @@ -609,6 +615,8 @@ def render_template_for(requested_details) child.instance_variable_set(:@__vc_ancestor_calls, vc_ancestor_calls) end + child.__vc_cache_dependencies = __vc_cache_dependencies.dup + super end diff --git a/test/sandbox/test/rendering_test.rb b/test/sandbox/test/rendering_test.rb index 8925a965a..4dd021503 100644 --- a/test/sandbox/test/rendering_test.rb +++ b/test/sandbox/test/rendering_test.rb @@ -1326,16 +1326,16 @@ def test_cache_component component = CacheComponent.new(foo: "foo", bar: "bar") render_inline(component) - assert_selector(".cache-component__cache-key", text: component.component_cache_dependencies) + assert_selector(".cache-component__cache-key", text: component.view_cache_dependencies) assert_selector(".cache-component__cache-message", text: "foo bar") render_inline(CacheComponent.new(foo: "foo", bar: "bar")) - assert_selector(".cache-component__cache-key", text: component.component_cache_dependencies) + assert_selector(".cache-component__cache-key", text: component.view_cache_dependencies) render_inline(CacheComponent.new(foo: "foo", bar: "baz")) - refute_selector(".cache-component__cache-key", text: component.component_cache_dependencies) + refute_selector(".cache-component__cache-key", text: component.view_cache_dependencies) refute_selector(".cache-component__cache-message", text: "foo bar") end end From fe41b05ea2ebab8309f8fef015fe495ba928cc88 Mon Sep 17 00:00:00 2001 From: Reegan Viljoen Date: Tue, 5 Nov 2024 10:53:10 +0200 Subject: [PATCH 08/70] fix lint --- lib/view_component/base.rb | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/lib/view_component/base.rb b/lib/view_component/base.rb index 5144bf736..2bc89d052 100644 --- a/lib/view_component/base.rb +++ b/lib/view_component/base.rb @@ -317,14 +317,6 @@ def virtual_path self.class.virtual_path end - # For caching, such as #cache_if - # @private - def view_cache_dependencies - self.class.view_cache_dependencies - end - - alias_method :component_cache_dependencies, :view_cache_dependencies - # For caching, such as #cache_if # # @private @@ -346,6 +338,9 @@ def __vc_request @__vc_request ||= controller.request if controller.respond_to?(:request) end + # For caching, such as #cache_if + # + # @private def view_cache_dependencies return unless __vc_cache_dependencies.present? && __vc_cache_dependencies.any? From a4158403771d869b76281f13fe7e3c34f6bdaced Mon Sep 17 00:00:00 2001 From: Reegan Viljoen Date: Tue, 5 Nov 2024 10:58:05 +0200 Subject: [PATCH 09/70] fix --- lib/view_component/base.rb | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/lib/view_component/base.rb b/lib/view_component/base.rb index 2bc89d052..37872bf5c 100644 --- a/lib/view_component/base.rb +++ b/lib/view_component/base.rb @@ -317,6 +317,15 @@ def virtual_path self.class.virtual_path end + # For caching, such as #cache_if + # + # @private + def view_cache_dependencies + return unless __vc_cache_dependencies.present? && __vc_cache_dependencies.any? + + __vc_cache_dependencies.filter_map { |dep| send(dep) } + end + # For caching, such as #cache_if # # @private From f8215a23887a00e48fd9c8dc9c3dd307df0f39bf Mon Sep 17 00:00:00 2001 From: Reegan Viljoen Date: Tue, 5 Nov 2024 22:38:02 +0200 Subject: [PATCH 10/70] modulerize code --- lib/view_component/base.rb | 13 ++------ lib/view_component/cacheable.rb | 57 +++++++++++++++++++++++++++++++++ 2 files changed, 59 insertions(+), 11 deletions(-) create mode 100644 lib/view_component/cacheable.rb diff --git a/lib/view_component/base.rb b/lib/view_component/base.rb index 37872bf5c..f510dc903 100644 --- a/lib/view_component/base.rb +++ b/lib/view_component/base.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true require "action_view" +require "view_component/cacheable" require "active_support/configurable" require "view_component/collection" require "view_component/compile_cache" @@ -55,6 +56,7 @@ def config include ViewComponent::Slotable include ViewComponent::Translatable include ViewComponent::WithContentHelper + include ViewComponent::Cacheable RESERVED_PARAMETER = :content VC_INTERNAL_DEFAULT_FORMAT = :html @@ -390,15 +392,6 @@ def __vc_render_in_block_provided? defined?(@view_context) && @view_context && @__vc_render_in_block end - def __vc_render_template(rendered_template) - # Avoid allocating new string when output_preamble and output_postamble are blank - if output_preamble.blank? && output_postamble.blank? - rendered_template - else - safe_output_preamble + rendered_template + safe_output_postamble - end - end - def __vc_content_set_by_with_content_defined? defined?(@__vc_content_set_by_with_content) end @@ -619,8 +612,6 @@ def render_template_for(requested_details) child.instance_variable_set(:@__vc_ancestor_calls, vc_ancestor_calls) end - child.__vc_cache_dependencies = __vc_cache_dependencies.dup - super end diff --git a/lib/view_component/cacheable.rb b/lib/view_component/cacheable.rb new file mode 100644 index 000000000..075a7eaed --- /dev/null +++ b/lib/view_component/cacheable.rb @@ -0,0 +1,57 @@ +# frozen_string_literal: true + +module ViewComponent::Cacheable + extend ActiveSupport::Concern + + included do + class_attribute :__vc_cache_dependencies, default: [] + + # For caching, such as #cache_if + # + # @private + def view_cache_dependencies + return unless __vc_cache_dependencies.present? && __vc_cache_dependencies.any? + + __vc_cache_dependencies.map { |dep| send(dep) }.compact + end + + # For handeling the output_preamble and output_postamble + # + # @private + def __vc_render_template(rendered_template) + # Avoid allocating new string when output_preamble and output_postamble are blank + if output_preamble.blank? && output_postamble.blank? + rendered_template + else + safe_output_preamble + rendered_template + safe_output_postamble + end + end + + # For determing if a template is rendered with cache or not + # + # @private + def __vc_render_cacheable(rendered_template) + if view_cache_dependencies.present? + Rails.cache.fetch(view_cache_dependencies) do + __vc_render_template(rendered_template) + end + else + __vc_render_template(rendered_template) + end + end + end + + class_methods do + + # For caching the component + def cache_on(*args) + __vc_cache_dependencies.push(*args) + end + + def inherited(child) + child.__vc_cache_dependencies = __vc_cache_dependencies.dup + + super + end + end +end From 05091d5e81450b7768b400e075e3cfaaa99e5658 Mon Sep 17 00:00:00 2001 From: Reegan Viljoen Date: Tue, 5 Nov 2024 22:43:19 +0200 Subject: [PATCH 11/70] more cleanup --- docs/CHANGELOG.md | 8 ++++---- lib/view_component/cacheable.rb | 1 - 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 42181a3a5..6ab7fc409 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -385,6 +385,10 @@ This release makes the following breaking changes: *JP Balarini* +* Add first class component cache. + + *Reegan Viljoen* + ## 3.21.0 * Updates testing docs to include an example of how to use with RSpec. @@ -457,10 +461,6 @@ This release makes the following breaking changes: *Javier Aranda* -* Add first class component cache. - - *Reegan Viljoen* - ## 3.17.0 * Use struct instead openstruct in lib code. diff --git a/lib/view_component/cacheable.rb b/lib/view_component/cacheable.rb index 075a7eaed..2328768aa 100644 --- a/lib/view_component/cacheable.rb +++ b/lib/view_component/cacheable.rb @@ -42,7 +42,6 @@ def __vc_render_cacheable(rendered_template) end class_methods do - # For caching the component def cache_on(*args) __vc_cache_dependencies.push(*args) From 3d22c2bf4848692efe500b09e194a62ef221c7b9 Mon Sep 17 00:00:00 2001 From: Reegan Viljoen <62689748+reeganviljoen@users.noreply.github.com> Date: Thu, 7 Nov 2024 07:46:39 +0200 Subject: [PATCH 12/70] Apply suggestions from code review Co-authored-by: Emil Kampp <40206+ekampp@users.noreply.github.com> --- lib/view_component/cacheable.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/view_component/cacheable.rb b/lib/view_component/cacheable.rb index 2328768aa..10581df02 100644 --- a/lib/view_component/cacheable.rb +++ b/lib/view_component/cacheable.rb @@ -12,7 +12,7 @@ module ViewComponent::Cacheable def view_cache_dependencies return unless __vc_cache_dependencies.present? && __vc_cache_dependencies.any? - __vc_cache_dependencies.map { |dep| send(dep) }.compact + __vc_cache_dependencies.filter_map { |dep| send(dep) } end # For handeling the output_preamble and output_postamble From f1773bd4131944824065b585c69ad661de5938c2 Mon Sep 17 00:00:00 2001 From: Reegan Viljoen <62689748+reeganviljoen@users.noreply.github.com> Date: Thu, 7 Nov 2024 07:46:49 +0200 Subject: [PATCH 13/70] Update lib/view_component/cacheable.rb Co-authored-by: Emil Kampp <40206+ekampp@users.noreply.github.com> --- lib/view_component/cacheable.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/view_component/cacheable.rb b/lib/view_component/cacheable.rb index 10581df02..09a5db885 100644 --- a/lib/view_component/cacheable.rb +++ b/lib/view_component/cacheable.rb @@ -10,7 +10,7 @@ module ViewComponent::Cacheable # # @private def view_cache_dependencies - return unless __vc_cache_dependencies.present? && __vc_cache_dependencies.any? + return if __vc_cache_dependencies.blank? || __vc_cache_dependencies.none? __vc_cache_dependencies.filter_map { |dep| send(dep) } end From ccc755abfc64243b54b018ac0c7520152954f4e6 Mon Sep 17 00:00:00 2001 From: reeganviljoen Date: Mon, 18 Nov 2024 23:05:56 +0200 Subject: [PATCH 14/70] fix alphebtization --- lib/view_component/base.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/view_component/base.rb b/lib/view_component/base.rb index f510dc903..b38ffd38e 100644 --- a/lib/view_component/base.rb +++ b/lib/view_component/base.rb @@ -51,7 +51,6 @@ def config include Rails.application.routes.url_helpers if defined?(Rails) && Rails.application include ERB::Escape include ActiveSupport::CoreExt::ERBUtil - include ViewComponent::InlineTemplate include ViewComponent::Slotable include ViewComponent::Translatable From c9622eb1eaf67edc23efd74331828b782aa33fdc Mon Sep 17 00:00:00 2001 From: reeganviljoen Date: Tue, 19 Nov 2024 22:17:06 +0200 Subject: [PATCH 15/70] add cache suhggestions --- test/sandbox/app/components/cache_component.html.erb | 2 +- .../app/controllers/integration_examples_controller.rb | 6 ++++++ test/sandbox/config/routes.rb | 1 + 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/test/sandbox/app/components/cache_component.html.erb b/test/sandbox/app/components/cache_component.html.erb index 802208680..9d20954cb 100644 --- a/test/sandbox/app/components/cache_component.html.erb +++ b/test/sandbox/app/components/cache_component.html.erb @@ -1,2 +1,2 @@

<%= view_cache_dependencies %>

-

<%= "#{foo} #{bar}" %>

+

"><%= "#{foo} #{bar}" %>

diff --git a/test/sandbox/app/controllers/integration_examples_controller.rb b/test/sandbox/app/controllers/integration_examples_controller.rb index 686328846..81b545fd2 100644 --- a/test/sandbox/app/controllers/integration_examples_controller.rb +++ b/test/sandbox/app/controllers/integration_examples_controller.rb @@ -11,6 +11,12 @@ def controller_inline render(ControllerInlineComponent.new(message: "bar")) end + def controller_inline_cached + foo = params[:foo] || "foo" + bar = params[:bar] || "bar" + render(CacheComponent.new(foo:, bar:)) + end + def controller_inline_with_block render(ControllerInlineWithBlockComponent.new(message: "bar").tap do |c| c.with_slot(name: "baz") diff --git a/test/sandbox/config/routes.rb b/test/sandbox/config/routes.rb index 284f15de5..256d5b99f 100644 --- a/test/sandbox/config/routes.rb +++ b/test/sandbox/config/routes.rb @@ -11,6 +11,7 @@ get :inline_products, to: "integration_examples#inline_products" get :cached, to: "integration_examples#cached" get :render_check, to: "integration_examples#render_check" + get :controller_inline_cached, to: "integration_examples#controller_inline_cached" get :controller_inline, to: "integration_examples#controller_inline" get :controller_inline_with_block, to: "integration_examples#controller_inline_with_block" get :controller_inline_baseline, to: "integration_examples#controller_inline_baseline" From d14263438727e84619dff77e4127b9b3722345d6 Mon Sep 17 00:00:00 2001 From: reeganviljoen Date: Thu, 21 Nov 2024 22:29:21 +0200 Subject: [PATCH 16/70] fix legacy ruby specs --- test/sandbox/app/controllers/integration_examples_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/sandbox/app/controllers/integration_examples_controller.rb b/test/sandbox/app/controllers/integration_examples_controller.rb index 81b545fd2..1aefaed49 100644 --- a/test/sandbox/app/controllers/integration_examples_controller.rb +++ b/test/sandbox/app/controllers/integration_examples_controller.rb @@ -14,7 +14,7 @@ def controller_inline def controller_inline_cached foo = params[:foo] || "foo" bar = params[:bar] || "bar" - render(CacheComponent.new(foo:, bar:)) + render(CacheComponent.new(foo: foo, bar: bar)) end def controller_inline_with_block From 10ffb42768c4bddd3c1520636ae4d5873abd9e76 Mon Sep 17 00:00:00 2001 From: Reegan Viljoen <62689748+reeganviljoen@users.noreply.github.com> Date: Sun, 23 Mar 2025 17:24:00 +0200 Subject: [PATCH 17/70] Apply suggestions from code review Co-authored-by: Joel Hawksley --- docs/CHANGELOG.md | 6 +----- lib/view_component/cacheable.rb | 4 ++-- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 6ab7fc409..5b7a11dde 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -381,11 +381,7 @@ This release makes the following breaking changes: *Reegan Viljoen* -* Add HomeStyler AI to list of companies using ViewComponent. - - *JP Balarini* - -* Add first class component cache. +* Add experimental support for caching. *Reegan Viljoen* diff --git a/lib/view_component/cacheable.rb b/lib/view_component/cacheable.rb index 09a5db885..14691ff34 100644 --- a/lib/view_component/cacheable.rb +++ b/lib/view_component/cacheable.rb @@ -15,7 +15,7 @@ def view_cache_dependencies __vc_cache_dependencies.filter_map { |dep| send(dep) } end - # For handeling the output_preamble and output_postamble + # For handling the output_preamble and output_postamble # # @private def __vc_render_template(rendered_template) @@ -27,7 +27,7 @@ def __vc_render_template(rendered_template) end end - # For determing if a template is rendered with cache or not + # Render component from cache if possible # # @private def __vc_render_cacheable(rendered_template) From 2c87f77e8c4248c9ff02398a7b0e27933566174f Mon Sep 17 00:00:00 2001 From: reeganviljoen Date: Wed, 26 Mar 2025 20:53:59 +0200 Subject: [PATCH 18/70] code review feedback --- test/sandbox/app/components/cache_component.html.erb | 1 + test/sandbox/app/components/cache_component.rb | 2 ++ test/sandbox/test/rendering_test.rb | 7 ++++--- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/test/sandbox/app/components/cache_component.html.erb b/test/sandbox/app/components/cache_component.html.erb index 9d20954cb..c0c5856a8 100644 --- a/test/sandbox/app/components/cache_component.html.erb +++ b/test/sandbox/app/components/cache_component.html.erb @@ -1,2 +1,3 @@

<%= view_cache_dependencies %>

+<%# <% binding.irb %>

"><%= "#{foo} #{bar}" %>

diff --git a/test/sandbox/app/components/cache_component.rb b/test/sandbox/app/components/cache_component.rb index 77fd73587..0236f0b47 100644 --- a/test/sandbox/app/components/cache_component.rb +++ b/test/sandbox/app/components/cache_component.rb @@ -1,6 +1,8 @@ # frozen_string_literal: true class CacheComponent < ViewComponent::Base + include ViewComponent::Cacheable + cache_on :foo, :bar attr_reader :foo, :bar diff --git a/test/sandbox/test/rendering_test.rb b/test/sandbox/test/rendering_test.rb index 4dd021503..d19f39404 100644 --- a/test/sandbox/test/rendering_test.rb +++ b/test/sandbox/test/rendering_test.rb @@ -1333,9 +1333,10 @@ def test_cache_component assert_selector(".cache-component__cache-key", text: component.view_cache_dependencies) - render_inline(CacheComponent.new(foo: "foo", bar: "baz")) + new_component = CacheComponent.new(foo: "foo", bar: "baz") + render_inline(new_component) - refute_selector(".cache-component__cache-key", text: component.view_cache_dependencies) - refute_selector(".cache-component__cache-message", text: "foo bar") + assert_selector(".cache-component__cache-key", text: new_component.view_cache_dependencies) + assert_selector(".cache-component__cache-message", text: "foo baz") end end From 2aa0b30e10bde0924a1ea6779e9fd31c5230adb3 Mon Sep 17 00:00:00 2001 From: reeganviljoen Date: Wed, 26 Mar 2025 21:05:12 +0200 Subject: [PATCH 19/70] make module fully optional; --- lib/view_component/cacheable.rb | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/lib/view_component/cacheable.rb b/lib/view_component/cacheable.rb index 14691ff34..a473b895e 100644 --- a/lib/view_component/cacheable.rb +++ b/lib/view_component/cacheable.rb @@ -15,17 +15,6 @@ def view_cache_dependencies __vc_cache_dependencies.filter_map { |dep| send(dep) } end - # For handling the output_preamble and output_postamble - # - # @private - def __vc_render_template(rendered_template) - # Avoid allocating new string when output_preamble and output_postamble are blank - if output_preamble.blank? && output_postamble.blank? - rendered_template - else - safe_output_preamble + rendered_template + safe_output_postamble - end - end # Render component from cache if possible # From d6a2516de1e314723255e40e7d7bb8503d5e0723 Mon Sep 17 00:00:00 2001 From: reeganviljoen Date: Wed, 26 Mar 2025 21:17:27 +0200 Subject: [PATCH 20/70] fix specs --- test/sandbox/app/components/inherited_cache_component.rb | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 test/sandbox/app/components/inherited_cache_component.rb diff --git a/test/sandbox/app/components/inherited_cache_component.rb b/test/sandbox/app/components/inherited_cache_component.rb new file mode 100644 index 000000000..e3c3b90dd --- /dev/null +++ b/test/sandbox/app/components/inherited_cache_component.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +class InheritedCacheComponent < CacheComponent + + def initialize(foo:, bar:) + super(foo: foo, bar: bar) + end +end From 6094406e1f0656892b4043e86921e766331ced1b Mon Sep 17 00:00:00 2001 From: reeganviljoen Date: Wed, 26 Mar 2025 21:19:11 +0200 Subject: [PATCH 21/70] fix lint --- lib/view_component/cacheable.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/view_component/cacheable.rb b/lib/view_component/cacheable.rb index a473b895e..979587c0b 100644 --- a/lib/view_component/cacheable.rb +++ b/lib/view_component/cacheable.rb @@ -15,7 +15,6 @@ def view_cache_dependencies __vc_cache_dependencies.filter_map { |dep| send(dep) } end - # Render component from cache if possible # # @private From e5de30e41288624404b8ebd4100c91ee07b9c54b Mon Sep 17 00:00:00 2001 From: reeganviljoen Date: Wed, 26 Mar 2025 21:25:15 +0200 Subject: [PATCH 22/70] fix coberage --- .../app/components/no_cache_component.html.erb | 4 ++++ test/sandbox/app/components/no_cache_component.rb | 12 ++++++++++++ test/sandbox/test/rendering_test.rb | 8 ++++++++ 3 files changed, 24 insertions(+) create mode 100644 test/sandbox/app/components/no_cache_component.html.erb create mode 100644 test/sandbox/app/components/no_cache_component.rb diff --git a/test/sandbox/app/components/no_cache_component.html.erb b/test/sandbox/app/components/no_cache_component.html.erb new file mode 100644 index 000000000..1f49d0aab --- /dev/null +++ b/test/sandbox/app/components/no_cache_component.html.erb @@ -0,0 +1,4 @@ +

<%= view_cache_dependencies %>

+<%# <% binding.irb %> + +

"><%= "#{foo} #{bar}" %>

diff --git a/test/sandbox/app/components/no_cache_component.rb b/test/sandbox/app/components/no_cache_component.rb new file mode 100644 index 000000000..4b078e19a --- /dev/null +++ b/test/sandbox/app/components/no_cache_component.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +class NoCacheComponent < ViewComponent::Base + include ViewComponent::Cacheable + + attr_reader :foo, :bar + + def initialize(foo:, bar:) + @foo = foo + @bar = bar + end +end diff --git a/test/sandbox/test/rendering_test.rb b/test/sandbox/test/rendering_test.rb index d19f39404..30f9a2e68 100644 --- a/test/sandbox/test/rendering_test.rb +++ b/test/sandbox/test/rendering_test.rb @@ -1339,4 +1339,12 @@ def test_cache_component assert_selector(".cache-component__cache-key", text: new_component.view_cache_dependencies) assert_selector(".cache-component__cache-message", text: "foo baz") end + + def test_no_cache_component + component = NoCacheComponent.new(foo: "foo", bar: "bar") + render_inline(NoCacheComponent.new(foo: "foo", bar: "bar")) + + assert_selector(".cache-component__cache-key", text: component.view_cache_dependencies) + assert_selector(".cache-component__cache-message", text: "foo bar") + end end From 8e971d5b13553178d98f311a81721712ca0b3aa2 Mon Sep 17 00:00:00 2001 From: reeganviljoen Date: Wed, 26 Mar 2025 21:31:23 +0200 Subject: [PATCH 23/70] add inherited component test --- docs/CHANGELOG.md | 2 +- .../app/components/cache_component.html.erb | 1 - .../inherited_cache_component.html.erb | 3 +++ .../components/inherited_cache_component.rb | 3 +-- .../app/components/no_cache_component.html.erb | 1 - test/sandbox/test/rendering_test.rb | 18 ++++++++++++++++++ 6 files changed, 23 insertions(+), 5 deletions(-) create mode 100644 test/sandbox/app/components/inherited_cache_component.html.erb diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 5b7a11dde..4f7e09677 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -389,7 +389,7 @@ This release makes the following breaking changes: * Updates testing docs to include an example of how to use with RSpec. - *Rylan Bowers* + *Rylanview_cache Bowers* * Add `--skip-suffix` option to component generator. diff --git a/test/sandbox/app/components/cache_component.html.erb b/test/sandbox/app/components/cache_component.html.erb index c0c5856a8..9d20954cb 100644 --- a/test/sandbox/app/components/cache_component.html.erb +++ b/test/sandbox/app/components/cache_component.html.erb @@ -1,3 +1,2 @@

<%= view_cache_dependencies %>

-<%# <% binding.irb %>

"><%= "#{foo} #{bar}" %>

diff --git a/test/sandbox/app/components/inherited_cache_component.html.erb b/test/sandbox/app/components/inherited_cache_component.html.erb new file mode 100644 index 000000000..fccbe87a4 --- /dev/null +++ b/test/sandbox/app/components/inherited_cache_component.html.erb @@ -0,0 +1,3 @@ +

<%= view_cache_dependencies %>

+ +

"><%= "#{foo} #{bar}" %>

diff --git a/test/sandbox/app/components/inherited_cache_component.rb b/test/sandbox/app/components/inherited_cache_component.rb index e3c3b90dd..c1de347a1 100644 --- a/test/sandbox/app/components/inherited_cache_component.rb +++ b/test/sandbox/app/components/inherited_cache_component.rb @@ -1,8 +1,7 @@ # frozen_string_literal: true class InheritedCacheComponent < CacheComponent - def initialize(foo:, bar:) - super(foo: foo, bar: bar) + super end end diff --git a/test/sandbox/app/components/no_cache_component.html.erb b/test/sandbox/app/components/no_cache_component.html.erb index 1f49d0aab..fccbe87a4 100644 --- a/test/sandbox/app/components/no_cache_component.html.erb +++ b/test/sandbox/app/components/no_cache_component.html.erb @@ -1,4 +1,3 @@

<%= view_cache_dependencies %>

-<%# <% binding.irb %>

"><%= "#{foo} #{bar}" %>

diff --git a/test/sandbox/test/rendering_test.rb b/test/sandbox/test/rendering_test.rb index 30f9a2e68..7fbb1e027 100644 --- a/test/sandbox/test/rendering_test.rb +++ b/test/sandbox/test/rendering_test.rb @@ -1340,6 +1340,24 @@ def test_cache_component assert_selector(".cache-component__cache-message", text: "foo baz") end + def test_cache_component + component = InheritedCacheComponent.new(foo: "foo", bar: "bar") + render_inline(component) + + assert_selector(".cache-component__cache-key", text: component.view_cache_dependencies) + assert_selector(".cache-component__cache-message", text: "foo bar") + + render_inline(InheritedCacheComponent.new(foo: "foo", bar: "bar")) + + assert_selector(".cache-component__cache-key", text: component.view_cache_dependencies) + + new_component = InheritedCacheComponent.new(foo: "foo", bar: "baz") + render_inline(new_component) + + assert_selector(".cache-component__cache-key", text: new_component.view_cache_dependencies) + assert_selector(".cache-component__cache-message", text: "foo baz") + end + def test_no_cache_component component = NoCacheComponent.new(foo: "foo", bar: "bar") render_inline(NoCacheComponent.new(foo: "foo", bar: "bar")) From f5c2fcef12c6d0ff758ffed1acd4e1d9653de5c9 Mon Sep 17 00:00:00 2001 From: reeganviljoen Date: Wed, 26 Mar 2025 21:34:47 +0200 Subject: [PATCH 24/70] fix tests --- test/sandbox/app/components/inherited_cache_component.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/sandbox/app/components/inherited_cache_component.rb b/test/sandbox/app/components/inherited_cache_component.rb index c1de347a1..fe025914a 100644 --- a/test/sandbox/app/components/inherited_cache_component.rb +++ b/test/sandbox/app/components/inherited_cache_component.rb @@ -2,6 +2,6 @@ class InheritedCacheComponent < CacheComponent def initialize(foo:, bar:) - super + super(foo: foo, bar: bar) end end From e3425a5bd0915930964f7f295f1d161cfb216286 Mon Sep 17 00:00:00 2001 From: reeganviljoen Date: Wed, 26 Mar 2025 21:51:51 +0200 Subject: [PATCH 25/70] merge inherited values --- lib/view_component/cacheable.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/view_component/cacheable.rb b/lib/view_component/cacheable.rb index 979587c0b..3daba6530 100644 --- a/lib/view_component/cacheable.rb +++ b/lib/view_component/cacheable.rb @@ -36,7 +36,7 @@ def cache_on(*args) end def inherited(child) - child.__vc_cache_dependencies = __vc_cache_dependencies.dup + child.__vc_cache_dependencies + __vc_cache_dependencies.dup if __vc_cache_dependencies.present? super end From bc894d627259c5a7ce141e5ca36f6df620d70e79 Mon Sep 17 00:00:00 2001 From: reeganviljoen Date: Wed, 26 Mar 2025 21:54:01 +0200 Subject: [PATCH 26/70] fix tests --- test/sandbox/test/rendering_test.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/sandbox/test/rendering_test.rb b/test/sandbox/test/rendering_test.rb index 7fbb1e027..20906bda3 100644 --- a/test/sandbox/test/rendering_test.rb +++ b/test/sandbox/test/rendering_test.rb @@ -1340,7 +1340,7 @@ def test_cache_component assert_selector(".cache-component__cache-message", text: "foo baz") end - def test_cache_component + def test_inherited_cache_component component = InheritedCacheComponent.new(foo: "foo", bar: "bar") render_inline(component) From b79c3ebe5754bd263d65fd209b599d48c36c6e3f Mon Sep 17 00:00:00 2001 From: reeganviljoen Date: Wed, 26 Mar 2025 21:55:21 +0200 Subject: [PATCH 27/70] fix lint --- test/sandbox/app/components/inherited_cache_component.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/sandbox/app/components/inherited_cache_component.rb b/test/sandbox/app/components/inherited_cache_component.rb index fe025914a..c1de347a1 100644 --- a/test/sandbox/app/components/inherited_cache_component.rb +++ b/test/sandbox/app/components/inherited_cache_component.rb @@ -2,6 +2,6 @@ class InheritedCacheComponent < CacheComponent def initialize(foo:, bar:) - super(foo: foo, bar: bar) + super end end From 60cf7520cb4cf271bf129eba6acad7bd89f2df8c Mon Sep 17 00:00:00 2001 From: reeganviljoen Date: Thu, 27 Mar 2025 20:03:32 +0200 Subject: [PATCH 28/70] add polish --- lib/view_component/cacheable.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/view_component/cacheable.rb b/lib/view_component/cacheable.rb index 3daba6530..a4541f325 100644 --- a/lib/view_component/cacheable.rb +++ b/lib/view_component/cacheable.rb @@ -12,7 +12,7 @@ module ViewComponent::Cacheable def view_cache_dependencies return if __vc_cache_dependencies.blank? || __vc_cache_dependencies.none? - __vc_cache_dependencies.filter_map { |dep| send(dep) } + __vc_cache_dependencies.filter_map { |dep| send(dep) }.join('-') end # Render component from cache if possible @@ -36,7 +36,7 @@ def cache_on(*args) end def inherited(child) - child.__vc_cache_dependencies + __vc_cache_dependencies.dup if __vc_cache_dependencies.present? + child.__vc_cache_dependencies = __vc_cache_dependencies.dup super end From 48a222fbf1aa282fb161cec86c41a9d0abbc1ea8 Mon Sep 17 00:00:00 2001 From: reeganviljoen Date: Thu, 27 Mar 2025 20:04:02 +0200 Subject: [PATCH 29/70] add wip docs --- docs/guide/caching.md | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 docs/guide/caching.md diff --git a/docs/guide/caching.md b/docs/guide/caching.md new file mode 100644 index 000000000..14f92c0de --- /dev/null +++ b/docs/guide/caching.md @@ -0,0 +1,42 @@ +--- +layout: default +title: Caching +parent: How-to guide +--- + +# Caching + +Experimental +{: .label } + +Components can implement caching by marking the depndencies that a digest can be built om using the cache_on macro, like so: + +```ruby +class CacheComponent < ViewComponent::Base + include ViewComponent::Cacheable + + cache_on :foo, :bar + attr_reader :foo, :bar + + def initialize(foo:, bar:) + @foo = foo + @bar = bar + end +end +``` + +```erb +

<%= view_cache_dependencies %>

+ +

<%= Time.zone.now %>">

+

<%= "#{foo} #{bar}" %>

+ +``` +will result in +```html +

foo-bar

+ +

2025-03-27 16:46:10 UTC

+

foo bar

+``` + From bf9ea17e9a7c0f861db763d211fd1ad2c511ae2e Mon Sep 17 00:00:00 2001 From: reeganviljoen Date: Thu, 27 Mar 2025 20:15:20 +0200 Subject: [PATCH 30/70] fix tests --- test/sandbox/test/rendering_test.rb | 26 -------------------------- 1 file changed, 26 deletions(-) diff --git a/test/sandbox/test/rendering_test.rb b/test/sandbox/test/rendering_test.rb index 20906bda3..d19f39404 100644 --- a/test/sandbox/test/rendering_test.rb +++ b/test/sandbox/test/rendering_test.rb @@ -1339,30 +1339,4 @@ def test_cache_component assert_selector(".cache-component__cache-key", text: new_component.view_cache_dependencies) assert_selector(".cache-component__cache-message", text: "foo baz") end - - def test_inherited_cache_component - component = InheritedCacheComponent.new(foo: "foo", bar: "bar") - render_inline(component) - - assert_selector(".cache-component__cache-key", text: component.view_cache_dependencies) - assert_selector(".cache-component__cache-message", text: "foo bar") - - render_inline(InheritedCacheComponent.new(foo: "foo", bar: "bar")) - - assert_selector(".cache-component__cache-key", text: component.view_cache_dependencies) - - new_component = InheritedCacheComponent.new(foo: "foo", bar: "baz") - render_inline(new_component) - - assert_selector(".cache-component__cache-key", text: new_component.view_cache_dependencies) - assert_selector(".cache-component__cache-message", text: "foo baz") - end - - def test_no_cache_component - component = NoCacheComponent.new(foo: "foo", bar: "bar") - render_inline(NoCacheComponent.new(foo: "foo", bar: "bar")) - - assert_selector(".cache-component__cache-key", text: component.view_cache_dependencies) - assert_selector(".cache-component__cache-message", text: "foo bar") - end end From f6f19b7be7fec4a44952b8edfd7e1a642a28573b Mon Sep 17 00:00:00 2001 From: reeganviljoen Date: Thu, 27 Mar 2025 20:18:40 +0200 Subject: [PATCH 31/70] fix lint --- lib/view_component/cacheable.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/view_component/cacheable.rb b/lib/view_component/cacheable.rb index a4541f325..bacb601ad 100644 --- a/lib/view_component/cacheable.rb +++ b/lib/view_component/cacheable.rb @@ -12,7 +12,7 @@ module ViewComponent::Cacheable def view_cache_dependencies return if __vc_cache_dependencies.blank? || __vc_cache_dependencies.none? - __vc_cache_dependencies.filter_map { |dep| send(dep) }.join('-') + __vc_cache_dependencies.filter_map { |dep| send(dep) }.join("-") end # Render component from cache if possible @@ -36,7 +36,7 @@ def cache_on(*args) end def inherited(child) - child.__vc_cache_dependencies = __vc_cache_dependencies.dup + child.__vc_cache_dependencies = __vc_cache_dependencies.dup super end From 87359f99dec984045a58da92e1d7e5e46f4a6136 Mon Sep 17 00:00:00 2001 From: reeganviljoen Date: Thu, 27 Mar 2025 20:40:56 +0200 Subject: [PATCH 32/70] fix coverage --- lib/view_component/cacheable.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/view_component/cacheable.rb b/lib/view_component/cacheable.rb index bacb601ad..717020298 100644 --- a/lib/view_component/cacheable.rb +++ b/lib/view_component/cacheable.rb @@ -10,9 +10,9 @@ module ViewComponent::Cacheable # # @private def view_cache_dependencies - return if __vc_cache_dependencies.blank? || __vc_cache_dependencies.none? + return if __vc_cache_dependencies.blank? || __vc_cache_dependencies.none? || __vc_cache_dependencies.nil? - __vc_cache_dependencies.filter_map { |dep| send(dep) }.join("-") + __vc_cache_dependencies.filter_map { |dep| send(dep) }.join('-') end # Render component from cache if possible From 4dcd622a46c8c972a3b8ed7ea295aa3d7cfdd5da Mon Sep 17 00:00:00 2001 From: reeganviljoen Date: Thu, 27 Mar 2025 20:43:56 +0200 Subject: [PATCH 33/70] fix lint --- docs/guide/caching.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/guide/caching.md b/docs/guide/caching.md index 14f92c0de..365536131 100644 --- a/docs/guide/caching.md +++ b/docs/guide/caching.md @@ -30,13 +30,13 @@ end

<%= Time.zone.now %>">

<%= "#{foo} #{bar}" %>

- ``` -will result in + +will result in: + ```html

foo-bar

2025-03-27 16:46:10 UTC

foo bar

``` - From 17389e37a5376766e63a265696292cf76fc72e53 Mon Sep 17 00:00:00 2001 From: reeganviljoen Date: Thu, 27 Mar 2025 20:44:04 +0200 Subject: [PATCH 34/70] fix lint --- lib/view_component/cacheable.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/view_component/cacheable.rb b/lib/view_component/cacheable.rb index 717020298..45e769a57 100644 --- a/lib/view_component/cacheable.rb +++ b/lib/view_component/cacheable.rb @@ -12,7 +12,7 @@ module ViewComponent::Cacheable def view_cache_dependencies return if __vc_cache_dependencies.blank? || __vc_cache_dependencies.none? || __vc_cache_dependencies.nil? - __vc_cache_dependencies.filter_map { |dep| send(dep) }.join('-') + __vc_cache_dependencies.filter_map { |dep| send(dep) }.join("-") end # Render component from cache if possible From a6f77109a422a78f52b8ed802ea8519a3be7f263 Mon Sep 17 00:00:00 2001 From: reeganviljoen Date: Tue, 1 Apr 2025 20:36:57 +0200 Subject: [PATCH 35/70] fix missing coverage --- test/sandbox/test/rendering_test.rb | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/test/sandbox/test/rendering_test.rb b/test/sandbox/test/rendering_test.rb index d19f39404..3c31ba640 100644 --- a/test/sandbox/test/rendering_test.rb +++ b/test/sandbox/test/rendering_test.rb @@ -1339,4 +1339,12 @@ def test_cache_component assert_selector(".cache-component__cache-key", text: new_component.view_cache_dependencies) assert_selector(".cache-component__cache-message", text: "foo baz") end + + def test_no_cache_compoennt + component = NoCacheComponent.new(foo: "foo", bar: "bar") + render_inline(component) + + assert_selector(".cache-component__cache-key", text: component.view_cache_dependencies) + assert_selector(".cache-component__cache-message", text: "foo bar") + end end From 13a44178ae0dc87203ec4c2eddda578e3cae8cdb Mon Sep 17 00:00:00 2001 From: reeganviljoen Date: Tue, 1 Apr 2025 20:55:57 +0200 Subject: [PATCH 36/70] add format and varaiant to cache_digest --- lib/view_component/cacheable.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/view_component/cacheable.rb b/lib/view_component/cacheable.rb index 45e769a57..ada29f8a8 100644 --- a/lib/view_component/cacheable.rb +++ b/lib/view_component/cacheable.rb @@ -18,8 +18,9 @@ def view_cache_dependencies # Render component from cache if possible # # @private - def __vc_render_cacheable(rendered_template) + def __vc_render_cacheable(rendered_template, variant = nil, format = nil) if view_cache_dependencies.present? + view_cache_dependencies = view_cache_dependencies + [variant, format] Rails.cache.fetch(view_cache_dependencies) do __vc_render_template(rendered_template) end From d9125553f99c593d89a1f25fd4c58f8ba76ffae9 Mon Sep 17 00:00:00 2001 From: reeganviljoen Date: Tue, 1 Apr 2025 21:27:37 +0200 Subject: [PATCH 37/70] add format and varaiant to cache_digest --- lib/view_component/cacheable.rb | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/lib/view_component/cacheable.rb b/lib/view_component/cacheable.rb index ada29f8a8..1905f933e 100644 --- a/lib/view_component/cacheable.rb +++ b/lib/view_component/cacheable.rb @@ -4,7 +4,7 @@ module ViewComponent::Cacheable extend ActiveSupport::Concern included do - class_attribute :__vc_cache_dependencies, default: [] + class_attribute :__vc_cache_dependencies, default: [:format, :__vc_format] # For caching, such as #cache_if # @@ -18,9 +18,8 @@ def view_cache_dependencies # Render component from cache if possible # # @private - def __vc_render_cacheable(rendered_template, variant = nil, format = nil) - if view_cache_dependencies.present? - view_cache_dependencies = view_cache_dependencies + [variant, format] + def __vc_render_cacheable(rendered_template) + if view_cache_dependencies != [:format, :__vc_format] Rails.cache.fetch(view_cache_dependencies) do __vc_render_template(rendered_template) end From 7413ad509c82985d4ceb51481f41cb73d9f70a08 Mon Sep 17 00:00:00 2001 From: reeganviljoen Date: Tue, 1 Apr 2025 21:32:54 +0200 Subject: [PATCH 38/70] fix coverage --- lib/view_component/cacheable.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/view_component/cacheable.rb b/lib/view_component/cacheable.rb index 1905f933e..851948d79 100644 --- a/lib/view_component/cacheable.rb +++ b/lib/view_component/cacheable.rb @@ -19,7 +19,7 @@ def view_cache_dependencies # # @private def __vc_render_cacheable(rendered_template) - if view_cache_dependencies != [:format, :__vc_format] + if __vc_cache_dependencies != [:format, :__vc_format] Rails.cache.fetch(view_cache_dependencies) do __vc_render_template(rendered_template) end From 7ed8d2868e6a29a77db160d0ddba588c87847dbf Mon Sep 17 00:00:00 2001 From: reeganviljoen Date: Thu, 1 May 2025 10:05:51 +0200 Subject: [PATCH 39/70] add retrive ccache key to be consistent with rails --- lib/view_component/cacheable.rb | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/lib/view_component/cacheable.rb b/lib/view_component/cacheable.rb index 851948d79..46b240439 100644 --- a/lib/view_component/cacheable.rb +++ b/lib/view_component/cacheable.rb @@ -12,7 +12,7 @@ module ViewComponent::Cacheable def view_cache_dependencies return if __vc_cache_dependencies.blank? || __vc_cache_dependencies.none? || __vc_cache_dependencies.nil? - __vc_cache_dependencies.filter_map { |dep| send(dep) }.join("-") + __vc_cache_dependencies.filter_map { |dep| retrieve_cache_key(send(dep)) }.join("&") end # Render component from cache if possible @@ -27,6 +27,18 @@ def __vc_render_cacheable(rendered_template) __vc_render_template(rendered_template) end end + + private + + def retrieve_cache_key(key) + case + when key.respond_to?(:cache_key_with_version) then key.cache_key_with_version + when key.respond_to?(:cache_key) then key.cache_key + when key.is_a?(Array) then key.map { |element| retrieve_cache_key(element) }.to_param + when key.respond_to?(:to_a) then retrieve_cache_key(key.to_a) + else key.to_param + end.to_s + end end class_methods do From b2807a78692ddcb83dd24b110448b7f3b86705ee Mon Sep 17 00:00:00 2001 From: reeganviljoen Date: Thu, 1 May 2025 12:36:20 +0200 Subject: [PATCH 40/70] Fix linting --- lib/view_component/cacheable.rb | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/lib/view_component/cacheable.rb b/lib/view_component/cacheable.rb index 46b240439..2bead11f4 100644 --- a/lib/view_component/cacheable.rb +++ b/lib/view_component/cacheable.rb @@ -28,17 +28,17 @@ def __vc_render_cacheable(rendered_template) end end - private - - def retrieve_cache_key(key) - case - when key.respond_to?(:cache_key_with_version) then key.cache_key_with_version - when key.respond_to?(:cache_key) then key.cache_key - when key.is_a?(Array) then key.map { |element| retrieve_cache_key(element) }.to_param - when key.respond_to?(:to_a) then retrieve_cache_key(key.to_a) - else key.to_param - end.to_s - end + private + + def retrieve_cache_key(key) + case + when key.respond_to?(:cache_key_with_version) then key.cache_key_with_version + when key.respond_to?(:cache_key) then key.cache_key + when key.is_a?(Array) then key.map { |element| retrieve_cache_key(element) }.to_param + when key.respond_to?(:to_a) then retrieve_cache_key(key.to_a) + else key.to_param + end.to_s + end end class_methods do From a1d84212c27c5050b2c55ce7aa823930cca1c0e4 Mon Sep 17 00:00:00 2001 From: reeganviljoen Date: Thu, 1 May 2025 12:48:57 +0200 Subject: [PATCH 41/70] fix changelog stuff --- docs/CHANGELOG.md | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 4f7e09677..e66833d3f 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -14,6 +14,10 @@ nav_order: 6 *Joel Hawksley*, *Blake Williams* +* Add experimental support for caching. + + *Reegan Viljoen* + ## 4.0.0.rc1 Almost six years after releasing [v1.0.0](https://github.com/ViewComponent/view_component/releases/tag/v1.0.0), we're proud to ship the first release candidate of 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. @@ -317,7 +321,7 @@ This release makes the following breaking changes: ## 3.23.0 -* Add docs about Slack channel in Ruby Central workspace. (Join us! #oss-view-component). Email joelhawksley@github.com for an invite. +* Add docs about Slack channel in Ruby Central workspace. (Join us! #oss-view-component). Email for an invite. *Joel Hawksley @@ -381,15 +385,15 @@ This release makes the following breaking changes: *Reegan Viljoen* -* Add experimental support for caching. +* Add HomeStyler AI to list of companies using ViewComponent. - *Reegan Viljoen* + *JP Balarini* ## 3.21.0 * Updates testing docs to include an example of how to use with RSpec. - *Rylanview_cache Bowers* + *Rylan Bowers* * Add `--skip-suffix` option to component generator. @@ -1773,7 +1777,7 @@ Run into an issue with this release? [Let us know](https://github.com/ViewCompon *Joel Hawksley* -* The ViewComponent team at GitHub is hiring! We're looking for a Rails engineer with accessibility experience: [https://boards.greenhouse.io/github/jobs/4020166](https://boards.greenhouse.io/github/jobs/4020166). Reach out to joelhawksley@github.com with any questions! +* The ViewComponent team at GitHub is hiring! We're looking for a Rails engineer with accessibility experience: [https://boards.greenhouse.io/github/jobs/4020166](https://boards.greenhouse.io/github/jobs/4020166). Reach out to with any questions! * The ViewComponent team is hosting a happy hour at RailsConf. Join us for snacks, drinks, and stickers: [https://www.eventbrite.com/e/viewcomponent-happy-hour-tickets-304168585427](https://www.eventbrite.com/e/viewcomponent-happy-hour-tickets-304168585427) @@ -2537,7 +2541,7 @@ Run into an issue with this release? [Let us know](https://github.com/ViewCompon *Matheus Richard* -* Are you interested in building the future of ViewComponent? GitHub is looking to hire a Senior Engineer to work on Primer ViewComponents and ViewComponent. Apply here: [US/Canada](https://github.com/careers) / [Europe](https://boards.greenhouse.io/github/jobs/3132294). Feel free to reach out to joelhawksley@github.com with any questions. +* Are you interested in building the future of ViewComponent? GitHub is looking to hire a Senior Engineer to work on Primer ViewComponents and ViewComponent. Apply here: [US/Canada](https://github.com/careers) / [Europe](https://boards.greenhouse.io/github/jobs/3132294). Feel free to reach out to with any questions. *Joel Hawksley* @@ -2555,7 +2559,7 @@ Run into an issue with this release? [Let us know](https://github.com/ViewCompon ## 2.31.0 -_Note: This release includes an underlying change to Slots that may affect incorrect usage of the API, where Slots were set on a line prefixed by `<%=`. The result of setting a Slot shouldn't be returned. (`<%`)_ +*Note: This release includes an underlying change to Slots that may affect incorrect usage of the API, where Slots were set on a line prefixed by `<%=`. The result of setting a Slot shouldn't be returned. (`<%`)* * Add `#with_content` to allow setting content without a block. @@ -3003,7 +3007,7 @@ _Note: This release includes an underlying change to Slots that may affect incor * The gem name is now `view_component`. * ViewComponent previews are now accessed at `/rails/view_components`. - * ViewComponents can _only_ be rendered with the instance syntax: `render(MyComponent.new)`. Support for all other syntaxes has been removed. + * ViewComponents can *only* be rendered with the instance syntax: `render(MyComponent.new)`. Support for all other syntaxes has been removed. * ActiveModel::Validations have been removed. ViewComponent generators no longer include validations. * In Rails 6.1, no monkey patching is used. * `to_component_class` has been removed. From d03928cf819d3ebe7b8baaa7a3fb6e88152b126a Mon Sep 17 00:00:00 2001 From: reeganviljoen Date: Fri, 2 May 2025 22:08:45 +0200 Subject: [PATCH 42/70] refactor cache logic --- lib/view_component/cacheable.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/view_component/cacheable.rb b/lib/view_component/cacheable.rb index 2bead11f4..4f70d1f61 100644 --- a/lib/view_component/cacheable.rb +++ b/lib/view_component/cacheable.rb @@ -12,7 +12,7 @@ module ViewComponent::Cacheable def view_cache_dependencies return if __vc_cache_dependencies.blank? || __vc_cache_dependencies.none? || __vc_cache_dependencies.nil? - __vc_cache_dependencies.filter_map { |dep| retrieve_cache_key(send(dep)) }.join("&") + retrieve_cache_key(__vc_cache_dependencies) end # Render component from cache if possible @@ -36,7 +36,7 @@ def retrieve_cache_key(key) when key.respond_to?(:cache_key) then key.cache_key when key.is_a?(Array) then key.map { |element| retrieve_cache_key(element) }.to_param when key.respond_to?(:to_a) then retrieve_cache_key(key.to_a) - else key.to_param + else public_send(key).to_param end.to_s end end From 64636b4267ba34747e372cc35e7ea7ea1f7dcc08 Mon Sep 17 00:00:00 2001 From: reeganviljoen Date: Fri, 2 May 2025 22:30:09 +0200 Subject: [PATCH 43/70] add identifier --- lib/view_component/cacheable.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/view_component/cacheable.rb b/lib/view_component/cacheable.rb index 4f70d1f61..257d83521 100644 --- a/lib/view_component/cacheable.rb +++ b/lib/view_component/cacheable.rb @@ -4,7 +4,7 @@ module ViewComponent::Cacheable extend ActiveSupport::Concern included do - class_attribute :__vc_cache_dependencies, default: [:format, :__vc_format] + class_attribute :__vc_cache_dependencies, default: [:format, :__vc_format, :identifier] # For caching, such as #cache_if # @@ -36,7 +36,7 @@ def retrieve_cache_key(key) when key.respond_to?(:cache_key) then key.cache_key when key.is_a?(Array) then key.map { |element| retrieve_cache_key(element) }.to_param when key.respond_to?(:to_a) then retrieve_cache_key(key.to_a) - else public_send(key).to_param + when respond_to?(key) then public_send(key).to_param end.to_s end end From 7876b14fc92157c8f29d7225d947f810ecbfb365 Mon Sep 17 00:00:00 2001 From: reeganviljoen Date: Mon, 5 May 2025 08:24:56 +0200 Subject: [PATCH 44/70] compuye cache keys --- lib/view_component/cacheable.rb | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/view_component/cacheable.rb b/lib/view_component/cacheable.rb index 257d83521..502cebab8 100644 --- a/lib/view_component/cacheable.rb +++ b/lib/view_component/cacheable.rb @@ -12,7 +12,8 @@ module ViewComponent::Cacheable def view_cache_dependencies return if __vc_cache_dependencies.blank? || __vc_cache_dependencies.none? || __vc_cache_dependencies.nil? - retrieve_cache_key(__vc_cache_dependencies) + computed_view_cache_dependencies = __vc_cache_dependencies.map { |dep| if respond_to?(dep) then public_send(dep) end } + retrieve_cache_key(computed_view_cache_dependencies) end # Render component from cache if possible @@ -35,8 +36,8 @@ def retrieve_cache_key(key) when key.respond_to?(:cache_key_with_version) then key.cache_key_with_version when key.respond_to?(:cache_key) then key.cache_key when key.is_a?(Array) then key.map { |element| retrieve_cache_key(element) }.to_param - when key.respond_to?(:to_a) then retrieve_cache_key(key.to_a) - when respond_to?(key) then public_send(key).to_param + when key.respond_to?(:to_a) then retrieve_cache_key(key.to_a) + else key.to_param end.to_s end end From 8a21be1133768fc21b679e5324faa8efc30fd1d9 Mon Sep 17 00:00:00 2001 From: reeganviljoen Date: Mon, 5 May 2025 08:31:09 +0200 Subject: [PATCH 45/70] fix lint --- lib/view_component/cacheable.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/view_component/cacheable.rb b/lib/view_component/cacheable.rb index 502cebab8..c739cd8a4 100644 --- a/lib/view_component/cacheable.rb +++ b/lib/view_component/cacheable.rb @@ -36,7 +36,7 @@ def retrieve_cache_key(key) when key.respond_to?(:cache_key_with_version) then key.cache_key_with_version when key.respond_to?(:cache_key) then key.cache_key when key.is_a?(Array) then key.map { |element| retrieve_cache_key(element) }.to_param - when key.respond_to?(:to_a) then retrieve_cache_key(key.to_a) + when key.respond_to?(:to_a) then retrieve_cache_key(key.to_a) else key.to_param end.to_s end From 540b2d87c008d550fe3c07c81605caff6bac7d86 Mon Sep 17 00:00:00 2001 From: reeganviljoen Date: Tue, 6 May 2025 18:03:51 +0200 Subject: [PATCH 46/70] refactor --- lib/view_component/cacheable.rb | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/lib/view_component/cacheable.rb b/lib/view_component/cacheable.rb index c739cd8a4..4bbe4d1a0 100644 --- a/lib/view_component/cacheable.rb +++ b/lib/view_component/cacheable.rb @@ -13,7 +13,7 @@ def view_cache_dependencies return if __vc_cache_dependencies.blank? || __vc_cache_dependencies.none? || __vc_cache_dependencies.nil? computed_view_cache_dependencies = __vc_cache_dependencies.map { |dep| if respond_to?(dep) then public_send(dep) end } - retrieve_cache_key(computed_view_cache_dependencies) + ActiveSupport::Cache.expand_cache_key(computed_view_cache_dependencies) end # Render component from cache if possible @@ -28,18 +28,6 @@ def __vc_render_cacheable(rendered_template) __vc_render_template(rendered_template) end end - - private - - def retrieve_cache_key(key) - case - when key.respond_to?(:cache_key_with_version) then key.cache_key_with_version - when key.respond_to?(:cache_key) then key.cache_key - when key.is_a?(Array) then key.map { |element| retrieve_cache_key(element) }.to_param - when key.respond_to?(:to_a) then retrieve_cache_key(key.to_a) - else key.to_param - end.to_s - end end class_methods do From 1b2988a71e60798db04f0313cf27a1ad79e9ca37 Mon Sep 17 00:00:00 2001 From: reeganviljoen Date: Tue, 6 May 2025 18:12:15 +0200 Subject: [PATCH 47/70] add set --- lib/view_component/cacheable.rb | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/view_component/cacheable.rb b/lib/view_component/cacheable.rb index 4bbe4d1a0..04e2a48c0 100644 --- a/lib/view_component/cacheable.rb +++ b/lib/view_component/cacheable.rb @@ -1,10 +1,12 @@ # frozen_string_literal: true +require 'set' + module ViewComponent::Cacheable extend ActiveSupport::Concern included do - class_attribute :__vc_cache_dependencies, default: [:format, :__vc_format, :identifier] + class_attribute :__vc_cache_dependencies, default: Set[:format, :__vc_format, :identifier] # For caching, such as #cache_if # @@ -33,7 +35,7 @@ def __vc_render_cacheable(rendered_template) class_methods do # For caching the component def cache_on(*args) - __vc_cache_dependencies.push(*args) + __vc_cache_dependencies.merge(args) end def inherited(child) From cf313a9973f054fe21af2a3577f268b8019646ba Mon Sep 17 00:00:00 2001 From: reeganviljoen Date: Tue, 6 May 2025 22:43:39 +0200 Subject: [PATCH 48/70] Add cache refistry and alighn cache with how action view does it --- lib/view_component/cache_registry.rb | 16 +++++++++++++ lib/view_component/cacheable.rb | 36 +++++++++++++++++++++++++--- 2 files changed, 49 insertions(+), 3 deletions(-) create mode 100644 lib/view_component/cache_registry.rb diff --git a/lib/view_component/cache_registry.rb b/lib/view_component/cache_registry.rb new file mode 100644 index 000000000..d11e0b48d --- /dev/null +++ b/lib/view_component/cache_registry.rb @@ -0,0 +1,16 @@ +module CachingRegistry # :nodoc: + extend self + + def caching? + ActiveSupport::IsolatedExecutionState[:action_view_caching] ||= false + end + + def track_caching + caching_was = ActiveSupport::IsolatedExecutionState[:action_view_caching] + ActiveSupport::IsolatedExecutionState[:action_view_caching] = true + + yield + ensure + ActiveSupport::IsolatedExecutionState[:action_view_caching] = caching_was + end +end diff --git a/lib/view_component/cacheable.rb b/lib/view_component/cacheable.rb index 04e2a48c0..ac0eedd3c 100644 --- a/lib/view_component/cacheable.rb +++ b/lib/view_component/cacheable.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true require 'set' +require 'view_component/cache_registry' module ViewComponent::Cacheable extend ActiveSupport::Concern @@ -15,7 +16,7 @@ def view_cache_dependencies return if __vc_cache_dependencies.blank? || __vc_cache_dependencies.none? || __vc_cache_dependencies.nil? computed_view_cache_dependencies = __vc_cache_dependencies.map { |dep| if respond_to?(dep) then public_send(dep) end } - ActiveSupport::Cache.expand_cache_key(computed_view_cache_dependencies) + combined_fragment_cache_key(ActiveSupport::Cache.expand_cache_key(computed_view_cache_dependencies)) end # Render component from cache if possible @@ -23,13 +24,42 @@ def view_cache_dependencies # @private def __vc_render_cacheable(rendered_template) if __vc_cache_dependencies != [:format, :__vc_format] - Rails.cache.fetch(view_cache_dependencies) do - __vc_render_template(rendered_template) + CachingRegistry.track_caching do + template_fragment(rendered_template) end else __vc_render_template(rendered_template) end end + + def template_fragment(rendered_template) + if content = read_fragment(rendered_template) + @view_renderer.cache_hits[@current_template&.virtual_path] = :hit if defined?(@view_renderer) + content + else + @view_renderer.cache_hits[@current_template&.virtual_path] = :miss if defined?(@view_renderer) + write_fragment(rendered_template) + end + end + + def read_fragment(rendered_template) + Rails.cache.fetch(view_cache_dependencies) + end + + def write_fragment(rendered_template) + content = __vc_render_template(rendered_template) + Rails.cache.fetch(view_cache_dependencies) do + content + end + content + end + + def combined_fragment_cache_key(key) + cache_key = [:view_component, ENV["RAILS_CACHE_ID"] || ENV["RAILS_APP_VERSION"], key] + cache_key.flatten!(1) + cache_key.compact! + cache_key + end end class_methods do From 03683fecb4190b7c52f281f6ea0ed73d7487b043 Mon Sep 17 00:00:00 2001 From: reeganviljoen Date: Tue, 6 May 2025 22:47:46 +0200 Subject: [PATCH 49/70] namespace registry --- lib/view_component/cache_registry.rb | 11 +++++++---- lib/view_component/cacheable.rb | 2 +- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/lib/view_component/cache_registry.rb b/lib/view_component/cache_registry.rb index d11e0b48d..45946549c 100644 --- a/lib/view_component/cache_registry.rb +++ b/lib/view_component/cache_registry.rb @@ -1,16 +1,19 @@ -module CachingRegistry # :nodoc: + +module ViewComponent + module CachingRegistry # :nodoc: extend self def caching? - ActiveSupport::IsolatedExecutionState[:action_view_caching] ||= false + ActiveSupport::IsolatedExecutionState[:view_component_caching] ||= false end def track_caching - caching_was = ActiveSupport::IsolatedExecutionState[:action_view_caching] + caching_was = ActiveSupport::IsolatedExecutionState[:view_component_caching] ActiveSupport::IsolatedExecutionState[:action_view_caching] = true yield ensure - ActiveSupport::IsolatedExecutionState[:action_view_caching] = caching_was + ActiveSupport::IsolatedExecutionState[:view_component_caching] = caching_was end + end end diff --git a/lib/view_component/cacheable.rb b/lib/view_component/cacheable.rb index ac0eedd3c..3d0846f3d 100644 --- a/lib/view_component/cacheable.rb +++ b/lib/view_component/cacheable.rb @@ -24,7 +24,7 @@ def view_cache_dependencies # @private def __vc_render_cacheable(rendered_template) if __vc_cache_dependencies != [:format, :__vc_format] - CachingRegistry.track_caching do + ViewComponent::CachingRegistry.track_caching do template_fragment(rendered_template) end else From a0c74eb6634d682114959bc7cef8e53c42f53a3f Mon Sep 17 00:00:00 2001 From: reeganviljoen Date: Tue, 6 May 2025 22:49:12 +0200 Subject: [PATCH 50/70] add magic comment --- lib/view_component/cache_registry.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/view_component/cache_registry.rb b/lib/view_component/cache_registry.rb index 45946549c..bbd643cb3 100644 --- a/lib/view_component/cache_registry.rb +++ b/lib/view_component/cache_registry.rb @@ -1,4 +1,5 @@ - +# frozen_string_literal: true + module ViewComponent module CachingRegistry # :nodoc: extend self From 8f45ac85c4941a77fd23f93b396dd34adf4ac6ff Mon Sep 17 00:00:00 2001 From: reeganviljoen Date: Wed, 7 May 2025 08:01:29 +0200 Subject: [PATCH 51/70] fix failing rails 6 specs --- test/sandbox/test/rendering_test.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/test/sandbox/test/rendering_test.rb b/test/sandbox/test/rendering_test.rb index 3c31ba640..854a81038 100644 --- a/test/sandbox/test/rendering_test.rb +++ b/test/sandbox/test/rendering_test.rb @@ -1323,6 +1323,8 @@ def test_around_render end def test_cache_component + return if Rails.version < "7.0" + component = CacheComponent.new(foo: "foo", bar: "bar") render_inline(component) @@ -1341,6 +1343,8 @@ def test_cache_component end def test_no_cache_compoennt + return if Rails.version < "7.0" + component = NoCacheComponent.new(foo: "foo", bar: "bar") render_inline(component) From 50943a1883ae6885e036b9be67a4285a3c124540 Mon Sep 17 00:00:00 2001 From: reeganviljoen Date: Wed, 7 May 2025 22:22:56 +0200 Subject: [PATCH 52/70] Add the start of an actual digestor --- lib/view_component/cache_digestor.rb | 19 +++++++++++++++++++ lib/view_component/cache_registry.rb | 4 ++-- lib/view_component/cacheable.rb | 16 +++++++++++----- 3 files changed, 32 insertions(+), 7 deletions(-) create mode 100644 lib/view_component/cache_digestor.rb diff --git a/lib/view_component/cache_digestor.rb b/lib/view_component/cache_digestor.rb new file mode 100644 index 000000000..b0309a8bc --- /dev/null +++ b/lib/view_component/cache_digestor.rb @@ -0,0 +1,19 @@ +# # frozen_string_literal: true + +module ViewComponent + class CacheDigestor + @@digest_mutex = Mutex.new + + class << self + def digest(name:, finder:, format: nil, dependencies: nil) + if dependencies.nil? || dependencies.empty? + cache_key = "#{name}.#{format}" + else + dependencies_suffix = dependencies.flatten.tap(&:compact!).join(".") + cache_key = "#{name}.#{format}.#{dependencies_suffix}" + end + cache_key + end + end + end +end diff --git a/lib/view_component/cache_registry.rb b/lib/view_component/cache_registry.rb index bbd643cb3..f90535c96 100644 --- a/lib/view_component/cache_registry.rb +++ b/lib/view_component/cache_registry.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true - + module ViewComponent - module CachingRegistry # :nodoc: + module CachingRegistry extend self def caching? diff --git a/lib/view_component/cacheable.rb b/lib/view_component/cacheable.rb index 3d0846f3d..9c5318d5d 100644 --- a/lib/view_component/cacheable.rb +++ b/lib/view_component/cacheable.rb @@ -1,7 +1,8 @@ # frozen_string_literal: true -require 'set' -require 'view_component/cache_registry' +require "set" +require "view_component/cache_registry" +require "view_component/cache_digestor" module ViewComponent::Cacheable extend ActiveSupport::Concern @@ -25,7 +26,7 @@ def view_cache_dependencies def __vc_render_cacheable(rendered_template) if __vc_cache_dependencies != [:format, :__vc_format] ViewComponent::CachingRegistry.track_caching do - template_fragment(rendered_template) + template_fragment(rendered_template) end else __vc_render_template(rendered_template) @@ -43,12 +44,12 @@ def template_fragment(rendered_template) end def read_fragment(rendered_template) - Rails.cache.fetch(view_cache_dependencies) + Rails.cache.fetch(component_digest) end def write_fragment(rendered_template) content = __vc_render_template(rendered_template) - Rails.cache.fetch(view_cache_dependencies) do + Rails.cache.fetch(component_digest) do content end content @@ -60,6 +61,11 @@ def combined_fragment_cache_key(key) cache_key.compact! cache_key end + + def component_digest + component_name = self.class.name.demodulize.underscore + ViewComponent::CacheDigestor.digest(name: component_name, format: format, finder: @lookup_context, dependencies: view_cache_dependencies) + end end class_methods do From da7c685e5afe9e4a87e98a83ad5b84bdaaa6e3ec Mon Sep 17 00:00:00 2001 From: reeganviljoen Date: Wed, 2 Jul 2025 16:30:02 +0200 Subject: [PATCH 53/70] add template digetor that usses an ast --- lib/view_component/cache_digestor.rb | 82 +++++++++++++++++-- lib/view_component/cacheable.rb | 33 ++++---- .../prism_render_dependency_extractor.rb | 70 ++++++++++++++++ .../templat_dependency_extractor.rb | 46 +++++++++++ lib/view_component/template_ast_builder.rb | 39 +++++++++ .../app/components/cache_component.html.erb | 2 + 6 files changed, 250 insertions(+), 22 deletions(-) create mode 100644 lib/view_component/prism_render_dependency_extractor.rb create mode 100644 lib/view_component/templat_dependency_extractor.rb create mode 100644 lib/view_component/template_ast_builder.rb diff --git a/lib/view_component/cache_digestor.rb b/lib/view_component/cache_digestor.rb index b0309a8bc..b9bf4a39b 100644 --- a/lib/view_component/cache_digestor.rb +++ b/lib/view_component/cache_digestor.rb @@ -1,19 +1,85 @@ # # frozen_string_literal: true +require 'view_component/templat_dependency_extractor' + module ViewComponent class CacheDigestor - @@digest_mutex = Mutex.new + def initialize(component:) + @component= component.class + end - class << self - def digest(name:, finder:, format: nil, dependencies: nil) - if dependencies.nil? || dependencies.empty? - cache_key = "#{name}.#{format}" + def digest + gather_templates + @templates.map do |template| + if template.type == :file + template_string = template.send(:source) + ViewComponent::TemplateDependencyExtractor.new(template_string, template.extension.to_sym).extract else - dependencies_suffix = dependencies.flatten.tap(&:compact!).join(".") - cache_key = "#{name}.#{format}.#{dependencies_suffix}" + # A digest cant be built for inline calls as there is no template to parse + [] end - cache_key end end + + def gather_templates + @templates ||= + begin + templates = @component.sidecar_files( + ActionView::Template.template_handler_extensions + ).map do |path| + # Extract format and variant from template filename + this_format, variant = + File + .basename(path) # "variants_component.html+mini.watch.erb" + .split(".")[1..-2] # ["html+mini", "watch"] + .join(".") # "html+mini.watch" + .split("+") # ["html", "mini.watch"] + .map(&:to_sym) # [:html, :"mini.watch"] + + out = Template.new( + component: @component, + type: :file, + path: path, + lineno: 0, + extension: path.split(".").last, + this_format: this_format.to_s.split(".").last&.to_sym, # strip locale from this_format, see #2113 + variant: variant + ) + + out + end + + component_instance_methods_on_self = @component.instance_methods(false) + + ( + @component.ancestors.take_while { |ancestor| ancestor != ViewComponent::Base } - @component.included_modules + ).flat_map { |ancestor| ancestor.instance_methods(false).grep(/^call(_|$)/) } + .uniq + .each do |method_name| + templates << Template.new( + component: @component, + type: :inline_call, + this_format: ViewComponent::Base::VC_INTERNAL_DEFAULT_FORMAT, + variant: method_name.to_s.include?("call_") ? method_name.to_s.sub("call_", "").to_sym : nil, + method_name: method_name, + defined_on_self: component_instance_methods_on_self.include?(method_name) + ) + end + + if @component.inline_template.present? + templates << Template.new( + component: @component, + type: :inline, + path: @component.inline_template.path, + lineno: @component.inline_template.lineno, + source: @component.inline_template.source.dup, + extension: @component.inline_template.language + ) + end + + templates + end + end + end end diff --git a/lib/view_component/cacheable.rb b/lib/view_component/cacheable.rb index 9c5318d5d..b58995955 100644 --- a/lib/view_component/cacheable.rb +++ b/lib/view_component/cacheable.rb @@ -8,23 +8,29 @@ module ViewComponent::Cacheable extend ActiveSupport::Concern included do - class_attribute :__vc_cache_dependencies, default: Set[:format, :__vc_format, :identifier] + + class_attribute :__vc_cache_options, default: Set[:identifier] + class_attribute :__vc_cache_dependencies, default: Set.new # For caching, such as #cache_if # # @private def view_cache_dependencies - return if __vc_cache_dependencies.blank? || __vc_cache_dependencies.none? || __vc_cache_dependencies.nil? + self.class.__vc_cache_dependencies.map { |dep| public_send(dep) } + end - computed_view_cache_dependencies = __vc_cache_dependencies.map { |dep| if respond_to?(dep) then public_send(dep) end } - combined_fragment_cache_key(ActiveSupport::Cache.expand_cache_key(computed_view_cache_dependencies)) + def view_cache_options + return if __vc_cache_options.blank? + + computed_view_cache_options = __vc_cache_options.map { |opt| if respond_to?(opt) then public_send(opt) end } + combined_fragment_cache_key(ActiveSupport::Cache.expand_cache_key(computed_view_cache_options + component_digest)) end # Render component from cache if possible # # @private def __vc_render_cacheable(rendered_template) - if __vc_cache_dependencies != [:format, :__vc_format] + if __vc_cache_options.any? ViewComponent::CachingRegistry.track_caching do template_fragment(rendered_template) end @@ -34,7 +40,7 @@ def __vc_render_cacheable(rendered_template) end def template_fragment(rendered_template) - if content = read_fragment(rendered_template) + if content = read_fragment @view_renderer.cache_hits[@current_template&.virtual_path] = :hit if defined?(@view_renderer) content else @@ -43,13 +49,13 @@ def template_fragment(rendered_template) end end - def read_fragment(rendered_template) - Rails.cache.fetch(component_digest) + def read_fragment + Rails.cache.fetch(view_cache_options) end def write_fragment(rendered_template) content = __vc_render_template(rendered_template) - Rails.cache.fetch(component_digest) do + Rails.cache.fetch(view_cache_options) do content end content @@ -63,20 +69,19 @@ def combined_fragment_cache_key(key) end def component_digest - component_name = self.class.name.demodulize.underscore - ViewComponent::CacheDigestor.digest(name: component_name, format: format, finder: @lookup_context, dependencies: view_cache_dependencies) + ViewComponent::CacheDigestor.new(component: self).digest end end class_methods do # For caching the component def cache_on(*args) - __vc_cache_dependencies.merge(args) + __vc_cache_options.merge(args) end def inherited(child) - child.__vc_cache_dependencies = __vc_cache_dependencies.dup - + child.__vc_cache_options = __vc_cache_options.dup + super end end diff --git a/lib/view_component/prism_render_dependency_extractor.rb b/lib/view_component/prism_render_dependency_extractor.rb new file mode 100644 index 000000000..e6ad92cbd --- /dev/null +++ b/lib/view_component/prism_render_dependency_extractor.rb @@ -0,0 +1,70 @@ + +# frozen_string_literal: true + +require 'prism' + +module ViewComponent + class PrismRenderDependencyExtractor + def initialize(code) + @code = code + @dependencies = [] + end + + def extract + result = Prism.parse(@code) + walk(result.value) + @dependencies + end + + private + + def walk(node) + return unless node.respond_to?(:child_nodes) + + if node.is_a?(Prism::CallNode) && render_call?(node) + extract_render_target(node) + end + + node.child_nodes.each { |child| walk(child) if child } + end + + def render_call?(node) + node.receiver.nil? && node.name == :render + end + + def extract_render_target(node) + args = node.arguments&.arguments + return unless args && !args.empty? + + first_arg = args.first + + if first_arg.is_a?(Prism::CallNode) && + first_arg.name == :new && + first_arg.receiver.is_a?(Prism::ConstantPathNode) || first_arg.receiver.is_a?(Prism::ConstantReadNode) + + const = extract_constant_path(first_arg.receiver) + @dependencies << const if const + end + end + + def extract_constant_path(const_node) + parts = [] + current = const_node + + while current + case current + when Prism::ConstantPathNode + parts.unshift(current.child.name) + current = current.parent + when Prism::ConstantReadNode + parts.unshift(current.name) + break + else + break + end + end + + parts.join("::") + end + end +end diff --git a/lib/view_component/templat_dependency_extractor.rb b/lib/view_component/templat_dependency_extractor.rb new file mode 100644 index 000000000..403a8fb62 --- /dev/null +++ b/lib/view_component/templat_dependency_extractor.rb @@ -0,0 +1,46 @@ + +# frozen_string_literal: true + +require_relative 'template_ast_builder' +require_relative 'prism_render_dependency_extractor' + +module ViewComponent + class TemplateDependencyExtractor + def initialize(template_string, engine) + @template_string = template_string + @engine = engine + @dependencies = [] + end + + def extract + ast = TemplateAstBuilder.build(@template_string, @engine) + walk(ast.split(';')) + @dependencies.uniq + end + + private + + def walk(node) + return unless node.is_a?(Array) + + node.each { extract_from_ruby(_1) if _1.is_a?(String) } + end + + def extract_from_ruby(ruby_code) + return unless ruby_code.include?("render") + + @dependencies.concat PrismRenderDependencyExtractor.new(ruby_code).extract + extract_partial_or_layout(ruby_code) + end + + def extract_partial_or_layout(code) + partial_match = code.match(/partial:\s*["']([^"']+)["']/) + layout_match = code.match(/layout:\s*["']([^"']+)["']/) + direct_render = code.match(/render\s*\(?\s*["']([^"']+)["']/) + + @dependencies << partial_match[1] if partial_match + @dependencies << layout_match[1] if layout_match + @dependencies << direct_render[1] if direct_render + end + end +end diff --git a/lib/view_component/template_ast_builder.rb b/lib/view_component/template_ast_builder.rb new file mode 100644 index 000000000..9918189ab --- /dev/null +++ b/lib/view_component/template_ast_builder.rb @@ -0,0 +1,39 @@ + +# frozen_string_literal: true + +require 'temple' +require 'slim' +require 'haml' +require 'erb' + +module ViewComponent + class TemplateAstBuilder + class HamlTempleWrapper < Temple::Engine + def call(template) + engine = Haml::Engine.new(template, format: :xhtml) + html = engine.render + [:multi, [:static, html]] + end + end + + class ErbTempleWrapper < Temple::Engine + def call(template) + Temple::ERB::Engine.new.call(template) + end + end + + ENGINE_MAP = { + slim: -> { Slim::Engine.new }, + haml: -> { HamlTempleWrapper.new }, + erb: -> { ErbTempleWrapper.new } + } + + def self.build(template_string, engine_name) + engine = ENGINE_MAP.fetch(engine_name.to_sym) do + raise ArgumentError, "Unsupported engine: #{engine_name.inspect}" + end.call + + engine.call(template_string) + end + end +end diff --git a/test/sandbox/app/components/cache_component.html.erb b/test/sandbox/app/components/cache_component.html.erb index 9d20954cb..1ba99c998 100644 --- a/test/sandbox/app/components/cache_component.html.erb +++ b/test/sandbox/app/components/cache_component.html.erb @@ -1,2 +1,4 @@

<%= view_cache_dependencies %>

"><%= "#{foo} #{bar}" %>

+ +<%= render(ButtonToComponent.new) %> From 40b50402b87fc6e1edae75f5851e00f82306dca9 Mon Sep 17 00:00:00 2001 From: reeganviljoen Date: Wed, 2 Jul 2025 16:49:07 +0200 Subject: [PATCH 54/70] refactor digetor a bit --- lib/view_component/cache_digestor.rb | 61 +--------------------------- 1 file changed, 2 insertions(+), 59 deletions(-) diff --git a/lib/view_component/cache_digestor.rb b/lib/view_component/cache_digestor.rb index b9bf4a39b..da1502c33 100644 --- a/lib/view_component/cache_digestor.rb +++ b/lib/view_component/cache_digestor.rb @@ -12,7 +12,7 @@ def digest gather_templates @templates.map do |template| if template.type == :file - template_string = template.send(:source) + template_string = template.source ViewComponent::TemplateDependencyExtractor.new(template_string, template.extension.to_sym).extract else # A digest cant be built for inline calls as there is no template to parse @@ -22,64 +22,7 @@ def digest end def gather_templates - @templates ||= - begin - templates = @component.sidecar_files( - ActionView::Template.template_handler_extensions - ).map do |path| - # Extract format and variant from template filename - this_format, variant = - File - .basename(path) # "variants_component.html+mini.watch.erb" - .split(".")[1..-2] # ["html+mini", "watch"] - .join(".") # "html+mini.watch" - .split("+") # ["html", "mini.watch"] - .map(&:to_sym) # [:html, :"mini.watch"] - - out = Template.new( - component: @component, - type: :file, - path: path, - lineno: 0, - extension: path.split(".").last, - this_format: this_format.to_s.split(".").last&.to_sym, # strip locale from this_format, see #2113 - variant: variant - ) - - out - end - - component_instance_methods_on_self = @component.instance_methods(false) - - ( - @component.ancestors.take_while { |ancestor| ancestor != ViewComponent::Base } - @component.included_modules - ).flat_map { |ancestor| ancestor.instance_methods(false).grep(/^call(_|$)/) } - .uniq - .each do |method_name| - templates << Template.new( - component: @component, - type: :inline_call, - this_format: ViewComponent::Base::VC_INTERNAL_DEFAULT_FORMAT, - variant: method_name.to_s.include?("call_") ? method_name.to_s.sub("call_", "").to_sym : nil, - method_name: method_name, - defined_on_self: component_instance_methods_on_self.include?(method_name) - ) - end - - if @component.inline_template.present? - templates << Template.new( - component: @component, - type: :inline, - path: @component.inline_template.path, - lineno: @component.inline_template.lineno, - source: @component.inline_template.source.dup, - extension: @component.inline_template.language - ) - end - - templates - end + @templates = @component.compiler.send(:gather_templates) end - end end From d9bb9f1befff6cd2e760d2a3a2f6c4c358d8dfea Mon Sep 17 00:00:00 2001 From: reeganviljoen Date: Wed, 2 Jul 2025 16:50:50 +0200 Subject: [PATCH 55/70] fix indentation --- lib/view_component/cache_digestor.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/view_component/cache_digestor.rb b/lib/view_component/cache_digestor.rb index da1502c33..9f2e32a29 100644 --- a/lib/view_component/cache_digestor.rb +++ b/lib/view_component/cache_digestor.rb @@ -5,7 +5,7 @@ module ViewComponent class CacheDigestor def initialize(component:) - @component= component.class + @component= component.class end def digest From 52607fcecd3a9b20d0bec2aa1fe67cb00d95cdd4 Mon Sep 17 00:00:00 2001 From: reeganviljoen Date: Thu, 3 Jul 2025 11:30:33 +0200 Subject: [PATCH 56/70] refactor --- lib/view_component/cache_digestor.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/view_component/cache_digestor.rb b/lib/view_component/cache_digestor.rb index 9f2e32a29..439a9dab5 100644 --- a/lib/view_component/cache_digestor.rb +++ b/lib/view_component/cache_digestor.rb @@ -22,7 +22,7 @@ def digest end def gather_templates - @templates = @component.compiler.send(:gather_templates) + @templates = @component.compiler.send(:gather_templates) end end end From c771954cd2cd7b175029553d894be95a13e6bf84 Mon Sep 17 00:00:00 2001 From: reeganviljoen Date: Thu, 3 Jul 2025 17:06:23 +0200 Subject: [PATCH 57/70] get pr up top date --- lib/view_component/base.rb | 32 ++----------------- lib/view_component/cache_digestor.rb | 23 +++++-------- lib/view_component/cacheable.rb | 20 ++++++------ lib/view_component/compiler.rb | 12 +++++-- .../templat_dependency_extractor.rb | 10 +++--- lib/view_component/template_ast_builder.rb | 13 ++++---- 6 files changed, 40 insertions(+), 70 deletions(-) diff --git a/lib/view_component/base.rb b/lib/view_component/base.rb index b38ffd38e..a02a97a95 100644 --- a/lib/view_component/base.rb +++ b/lib/view_component/base.rb @@ -75,7 +75,7 @@ def config # Config option that strips trailing whitespace in templates before compiling them. class_attribute :__vc_response_format, instance_accessor: false, instance_predicate: false, default: nil - class_attribute :__vc_cache_dependencies, instance_accessor: false, instance_predicate: false, default: [] + class_attribute :__vc_strip_trailing_whitespace, instance_accessor: false, instance_predicate: false self.__vc_strip_trailing_whitespace = false # class_attribute:default doesn't work until Rails 5.2 @@ -318,15 +318,6 @@ def virtual_path self.class.virtual_path end - # For caching, such as #cache_if - # - # @private - def view_cache_dependencies - return unless __vc_cache_dependencies.present? && __vc_cache_dependencies.any? - - __vc_cache_dependencies.filter_map { |dep| send(dep) } - end - # For caching, such as #cache_if # # @private @@ -348,15 +339,6 @@ def __vc_request @__vc_request ||= controller.request if controller.respond_to?(:request) end - # For caching, such as #cache_if - # - # @private - def view_cache_dependencies - return unless __vc_cache_dependencies.present? && __vc_cache_dependencies.any? - - __vc_cache_dependencies.filter_map { |dep| send(dep) } - end - # The content passed to the component instance as a block. # # @return [String] @@ -372,7 +354,7 @@ def content end end - # Whether `content` has been passed to the component. + # Whether f render?`content` has been passed to the component. # # @return [Boolean] def content? @@ -543,16 +525,6 @@ def sidecar_files(extensions) (sidecar_files - [identifier] + sidecar_directory_files + nested_component_files).uniq end - def cache_on(*args) - __vc_cache_dependencies.push(*args) - end - - def view_cache_dependencies - return unless __vc_cache_dependencies.any? - - __vc_cache_dependencies.filter_map { |dep| send(dep) } - end - # Render a component for each element in a collection ([documentation](/guide/collections)): # # ```ruby diff --git a/lib/view_component/cache_digestor.rb b/lib/view_component/cache_digestor.rb index 439a9dab5..d7588c49f 100644 --- a/lib/view_component/cache_digestor.rb +++ b/lib/view_component/cache_digestor.rb @@ -1,28 +1,21 @@ # # frozen_string_literal: true -require 'view_component/templat_dependency_extractor' +require "view_component/templat_dependency_extractor" module ViewComponent class CacheDigestor def initialize(component:) - @component= component.class + @component = component end def digest - gather_templates - @templates.map do |template| - if template.type == :file - template_string = template.source - ViewComponent::TemplateDependencyExtractor.new(template_string, template.extension.to_sym).extract - else - # A digest cant be built for inline calls as there is no template to parse - [] - end + template = @component.current_template + if template.nil? && template == :inline_call + [] + else + template_string = template.source + ViewComponent::TemplateDependencyExtractor.new(template_string, template.details.handler).extract end end - - def gather_templates - @templates = @component.compiler.send(:gather_templates) - end end end diff --git a/lib/view_component/cacheable.rb b/lib/view_component/cacheable.rb index b58995955..11ca36c8a 100644 --- a/lib/view_component/cacheable.rb +++ b/lib/view_component/cacheable.rb @@ -1,6 +1,5 @@ # frozen_string_literal: true -require "set" require "view_component/cache_registry" require "view_component/cache_digestor" @@ -8,7 +7,6 @@ module ViewComponent::Cacheable extend ActiveSupport::Concern included do - class_attribute :__vc_cache_options, default: Set[:identifier] class_attribute :__vc_cache_dependencies, default: Set.new @@ -29,23 +27,23 @@ def view_cache_options # Render component from cache if possible # # @private - def __vc_render_cacheable(rendered_template) - if __vc_cache_options.any? + def __vc_render_cacheable(safe_call) + if (__vc_cache_options - [:identifier]).any? ViewComponent::CachingRegistry.track_caching do - template_fragment(rendered_template) + template_fragment(safe_call) end else - __vc_render_template(rendered_template) + instance_exec(&safe_call) end end - def template_fragment(rendered_template) + def template_fragment if content = read_fragment @view_renderer.cache_hits[@current_template&.virtual_path] = :hit if defined?(@view_renderer) content else @view_renderer.cache_hits[@current_template&.virtual_path] = :miss if defined?(@view_renderer) - write_fragment(rendered_template) + write_fragment end end @@ -53,8 +51,8 @@ def read_fragment Rails.cache.fetch(view_cache_options) end - def write_fragment(rendered_template) - content = __vc_render_template(rendered_template) + def write_fragment + content = instance_exec(&safe_call) Rails.cache.fetch(view_cache_options) do content end @@ -81,7 +79,7 @@ def cache_on(*args) def inherited(child) child.__vc_cache_options = __vc_cache_options.dup - + super end end diff --git a/lib/view_component/compiler.rb b/lib/view_component/compiler.rb index d972e7611..8fe2973a7 100644 --- a/lib/view_component/compiler.rb +++ b/lib/view_component/compiler.rb @@ -97,13 +97,21 @@ def define_render_template_for safe_call = template.safe_method_name_call @component.define_method(:render_template_for) do |_| @current_template = template - instance_exec(&safe_call) + if @component.respond_to?(:__vc_render_cacheable) + @component.__vc_render_cacheable(safe_call) + else + instance_exec(&safe_call) + end end else compiler = self @component.define_method(:render_template_for) do |details| if (@current_template = compiler.find_templates_for(details).first) - instance_exec(&@current_template.safe_method_name_call) + if @component.respond_to?(:__vc_render_cacheable) + @component.__vc_render_cacheable(@current_template.safe_method_name_call) + else + instance_exec(&@current_template.safe_method_name_call) + end else raise MissingTemplateError.new(self.class.name, details) end diff --git a/lib/view_component/templat_dependency_extractor.rb b/lib/view_component/templat_dependency_extractor.rb index 403a8fb62..46dc0a460 100644 --- a/lib/view_component/templat_dependency_extractor.rb +++ b/lib/view_component/templat_dependency_extractor.rb @@ -1,8 +1,7 @@ - # frozen_string_literal: true -require_relative 'template_ast_builder' -require_relative 'prism_render_dependency_extractor' +require_relative "template_ast_builder" +require_relative "prism_render_dependency_extractor" module ViewComponent class TemplateDependencyExtractor @@ -14,7 +13,8 @@ def initialize(template_string, engine) def extract ast = TemplateAstBuilder.build(@template_string, @engine) - walk(ast.split(';')) + return @dependencies unless ast.present? + walk(ast.split(";")) @dependencies.uniq end @@ -35,7 +35,7 @@ def extract_from_ruby(ruby_code) def extract_partial_or_layout(code) partial_match = code.match(/partial:\s*["']([^"']+)["']/) - layout_match = code.match(/layout:\s*["']([^"']+)["']/) + layout_match = code.match(/layout:\s*["']([^"']+)["']/) direct_render = code.match(/render\s*\(?\s*["']([^"']+)["']/) @dependencies << partial_match[1] if partial_match diff --git a/lib/view_component/template_ast_builder.rb b/lib/view_component/template_ast_builder.rb index 9918189ab..dc40dcaeb 100644 --- a/lib/view_component/template_ast_builder.rb +++ b/lib/view_component/template_ast_builder.rb @@ -1,10 +1,9 @@ - # frozen_string_literal: true -require 'temple' -require 'slim' -require 'haml' -require 'erb' +require "temple" +require "slim" +require "haml" +require "erb" module ViewComponent class TemplateAstBuilder @@ -25,12 +24,12 @@ def call(template) ENGINE_MAP = { slim: -> { Slim::Engine.new }, haml: -> { HamlTempleWrapper.new }, - erb: -> { ErbTempleWrapper.new } + erb: -> { ErbTempleWrapper.new } } def self.build(template_string, engine_name) engine = ENGINE_MAP.fetch(engine_name.to_sym) do - raise ArgumentError, "Unsupported engine: #{engine_name.inspect}" + return nil end.call engine.call(template_string) From 5af2bc7eb2572395e71fdafe9a214e7a9402df60 Mon Sep 17 00:00:00 2001 From: reeganviljoen Date: Thu, 3 Jul 2025 17:12:01 +0200 Subject: [PATCH 58/70] fix merge issue --- lib/view_component/base.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/view_component/base.rb b/lib/view_component/base.rb index a02a97a95..7a17d412e 100644 --- a/lib/view_component/base.rb +++ b/lib/view_component/base.rb @@ -321,8 +321,8 @@ def virtual_path # For caching, such as #cache_if # # @private - def format - @__vc_variant if defined?(@__vc_variant) + def view_cache_dependencies + [] end # The current request. Use sparingly as doing so introduces coupling that From 9d88e3a4eb64a3f3aeab6918af13115eeadfa584 Mon Sep 17 00:00:00 2001 From: reeganviljoen Date: Thu, 3 Jul 2025 17:15:02 +0200 Subject: [PATCH 59/70] try get tests to pass --- lib/view_component/cacheable.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/view_component/cacheable.rb b/lib/view_component/cacheable.rb index 11ca36c8a..2c6eeaf4f 100644 --- a/lib/view_component/cacheable.rb +++ b/lib/view_component/cacheable.rb @@ -10,6 +10,7 @@ module ViewComponent::Cacheable class_attribute :__vc_cache_options, default: Set[:identifier] class_attribute :__vc_cache_dependencies, default: Set.new + silence_redefinition_of_method(:view_cache_dependencies) # For caching, such as #cache_if # # @private From 3542bebae7faa3db04c764068aec6182b49f4b3e Mon Sep 17 00:00:00 2001 From: reeganviljoen Date: Thu, 3 Jul 2025 17:18:23 +0200 Subject: [PATCH 60/70] try get tests to pass --- lib/view_component/base.rb | 7 ------- lib/view_component/cacheable.rb | 1 - 2 files changed, 8 deletions(-) diff --git a/lib/view_component/base.rb b/lib/view_component/base.rb index 7a17d412e..8572f92ad 100644 --- a/lib/view_component/base.rb +++ b/lib/view_component/base.rb @@ -318,13 +318,6 @@ def virtual_path self.class.virtual_path end - # For caching, such as #cache_if - # - # @private - def view_cache_dependencies - [] - end - # The current request. Use sparingly as doing so introduces coupling that # inhibits encapsulation & reuse, often making testing difficult. # diff --git a/lib/view_component/cacheable.rb b/lib/view_component/cacheable.rb index 2c6eeaf4f..11ca36c8a 100644 --- a/lib/view_component/cacheable.rb +++ b/lib/view_component/cacheable.rb @@ -10,7 +10,6 @@ module ViewComponent::Cacheable class_attribute :__vc_cache_options, default: Set[:identifier] class_attribute :__vc_cache_dependencies, default: Set.new - silence_redefinition_of_method(:view_cache_dependencies) # For caching, such as #cache_if # # @private From 4f58de4d2cdbb31708e0c49d72ec60ee3c7d99b3 Mon Sep 17 00:00:00 2001 From: reeganviljoen Date: Thu, 3 Jul 2025 17:20:21 +0200 Subject: [PATCH 61/70] fix rails 8 test --- test/sandbox/test/rendering_test.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/sandbox/test/rendering_test.rb b/test/sandbox/test/rendering_test.rb index 854a81038..1dcd215b2 100644 --- a/test/sandbox/test/rendering_test.rb +++ b/test/sandbox/test/rendering_test.rb @@ -20,7 +20,7 @@ def test_render_inline_allocations MyComponent.__vc_ensure_compiled with_instrumentation_enabled_option(false) do - assert_allocations({"3.5" => 69, "3.4" => 74, "3.3" => 72, "3.2" => 71}) do + assert_allocations({"3.5" => 68, "3.4" => 74, "3.3" => 72, "3.2" => 71}) do render_inline(MyComponent.new) end end From b75e0c2f0b6bc6a0bbbf3813b11c784a5eaaf042 Mon Sep 17 00:00:00 2001 From: reeganviljoen Date: Thu, 3 Jul 2025 17:26:07 +0200 Subject: [PATCH 62/70] fix soem artifcats --- .tool-versions | 2 +- lib/view_component/base.rb | 5 +---- ...endency_extractor.rb => template_dependency_extractor.rb} | 0 3 files changed, 2 insertions(+), 5 deletions(-) rename lib/view_component/{templat_dependency_extractor.rb => template_dependency_extractor.rb} (100%) diff --git a/.tool-versions b/.tool-versions index ae5ecdb2b..a72ead61f 100644 --- a/.tool-versions +++ b/.tool-versions @@ -1 +1 @@ -ruby 3.4.2 +ruby 3.4.3 diff --git a/lib/view_component/base.rb b/lib/view_component/base.rb index 8572f92ad..bce609be9 100644 --- a/lib/view_component/base.rb +++ b/lib/view_component/base.rb @@ -57,9 +57,6 @@ def config include ViewComponent::WithContentHelper include ViewComponent::Cacheable - RESERVED_PARAMETER = :content - VC_INTERNAL_DEFAULT_FORMAT = :html - # For CSRF authenticity tokens in forms delegate :form_authenticity_token, :protect_against_forgery?, :config, to: :helpers @@ -347,7 +344,7 @@ def content end end - # Whether f render?`content` has been passed to the component. + # Whether `content` has been passed to the component. # # @return [Boolean] def content? diff --git a/lib/view_component/templat_dependency_extractor.rb b/lib/view_component/template_dependency_extractor.rb similarity index 100% rename from lib/view_component/templat_dependency_extractor.rb rename to lib/view_component/template_dependency_extractor.rb From 1d81e9dd7a07d2cbbf81f9c96d32dd8d34c30eec Mon Sep 17 00:00:00 2001 From: reeganviljoen Date: Thu, 3 Jul 2025 17:27:30 +0200 Subject: [PATCH 63/70] fix test --- lib/view_component/cache_digestor.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/view_component/cache_digestor.rb b/lib/view_component/cache_digestor.rb index d7588c49f..bf98bcaee 100644 --- a/lib/view_component/cache_digestor.rb +++ b/lib/view_component/cache_digestor.rb @@ -1,6 +1,6 @@ # # frozen_string_literal: true -require "view_component/templat_dependency_extractor" +require "view_component/template_dependency_extractor" module ViewComponent class CacheDigestor From 35f68a85d04b2f06c5acde4374e4278487113fa5 Mon Sep 17 00:00:00 2001 From: reeganviljoen Date: Thu, 3 Jul 2025 17:37:24 +0200 Subject: [PATCH 64/70] make primer pass --- Gemfile.lock | 1 + view_component.gemspec | 1 + 2 files changed, 2 insertions(+) diff --git a/Gemfile.lock b/Gemfile.lock index f4dada8f5..c3ebd8e04 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -4,6 +4,7 @@ PATH view_component (4.0.0.rc1) activesupport (>= 7.1.0, < 8.1) concurrent-ruby (~> 1) + temple (~> 0.10) GEM remote: https://rubygems.org/ diff --git a/view_component.gemspec b/view_component.gemspec index 8154e9f65..c1b0a2278 100644 --- a/view_component.gemspec +++ b/view_component.gemspec @@ -34,4 +34,5 @@ Gem::Specification.new do |spec| spec.add_runtime_dependency "activesupport", [">= 7.1.0", "< 8.1"] spec.add_runtime_dependency "concurrent-ruby", "~> 1" + spec.add_runtime_dependency "temple", "~> 0.10" end From 1102a50be87ef4ffe5cb7ed44c5e5b3e56ae8531 Mon Sep 17 00:00:00 2001 From: reeganviljoen Date: Thu, 3 Jul 2025 17:38:53 +0200 Subject: [PATCH 65/70] fix linting --- lib/view_component/prism_render_dependency_extractor.rb | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/lib/view_component/prism_render_dependency_extractor.rb b/lib/view_component/prism_render_dependency_extractor.rb index e6ad92cbd..318b503e6 100644 --- a/lib/view_component/prism_render_dependency_extractor.rb +++ b/lib/view_component/prism_render_dependency_extractor.rb @@ -1,7 +1,6 @@ - # frozen_string_literal: true -require 'prism' +require "prism" module ViewComponent class PrismRenderDependencyExtractor @@ -39,8 +38,8 @@ def extract_render_target(node) first_arg = args.first if first_arg.is_a?(Prism::CallNode) && - first_arg.name == :new && - first_arg.receiver.is_a?(Prism::ConstantPathNode) || first_arg.receiver.is_a?(Prism::ConstantReadNode) + first_arg.name == :new && + first_arg.receiver.is_a?(Prism::ConstantPathNode) || first_arg.receiver.is_a?(Prism::ConstantReadNode) const = extract_constant_path(first_arg.receiver) @dependencies << const if const From b5a65874c52783b65e3efc23b70b24ed949cc7e4 Mon Sep 17 00:00:00 2001 From: reeganviljoen Date: Thu, 3 Jul 2025 17:41:47 +0200 Subject: [PATCH 66/70] fix linting --- lib/view_component/cacheable.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/view_component/cacheable.rb b/lib/view_component/cacheable.rb index 11ca36c8a..e0c73925c 100644 --- a/lib/view_component/cacheable.rb +++ b/lib/view_component/cacheable.rb @@ -38,7 +38,7 @@ def __vc_render_cacheable(safe_call) end def template_fragment - if content = read_fragment + if (content = read_fragment) @view_renderer.cache_hits[@current_template&.virtual_path] = :hit if defined?(@view_renderer) content else From 299ff9d63e08307b2585f092df252fbe21d5f18c Mon Sep 17 00:00:00 2001 From: reeganviljoen Date: Thu, 3 Jul 2025 17:43:47 +0200 Subject: [PATCH 67/70] fix alloactor spec --- test/sandbox/test/rendering_test.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/sandbox/test/rendering_test.rb b/test/sandbox/test/rendering_test.rb index 1dcd215b2..d54bec2ca 100644 --- a/test/sandbox/test/rendering_test.rb +++ b/test/sandbox/test/rendering_test.rb @@ -20,7 +20,7 @@ def test_render_inline_allocations MyComponent.__vc_ensure_compiled with_instrumentation_enabled_option(false) do - assert_allocations({"3.5" => 68, "3.4" => 74, "3.3" => 72, "3.2" => 71}) do + assert_allocations({"3.5" => 68, "3.4" => 76, "3.3" => 72, "3.2" => 71}) do render_inline(MyComponent.new) end end From 9aa9dd0efb68f131e80cd415dce6c2b83a74327c Mon Sep 17 00:00:00 2001 From: reeganviljoen Date: Thu, 3 Jul 2025 20:18:46 +0200 Subject: [PATCH 68/70] fix tests --- test/sandbox/test/rendering_test.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/sandbox/test/rendering_test.rb b/test/sandbox/test/rendering_test.rb index d54bec2ca..1dcd215b2 100644 --- a/test/sandbox/test/rendering_test.rb +++ b/test/sandbox/test/rendering_test.rb @@ -20,7 +20,7 @@ def test_render_inline_allocations MyComponent.__vc_ensure_compiled with_instrumentation_enabled_option(false) do - assert_allocations({"3.5" => 68, "3.4" => 76, "3.3" => 72, "3.2" => 71}) do + assert_allocations({"3.5" => 68, "3.4" => 74, "3.3" => 72, "3.2" => 71}) do render_inline(MyComponent.new) end end From f2df73574ce0c8cc1315cfc14f345c2de63e6386 Mon Sep 17 00:00:00 2001 From: reeganviljoen Date: Thu, 3 Jul 2025 20:23:22 +0200 Subject: [PATCH 69/70] make primer pass --- Gemfile.lock | 2 ++ view_component.gemspec | 2 ++ 2 files changed, 4 insertions(+) diff --git a/Gemfile.lock b/Gemfile.lock index c3ebd8e04..b674e7e0f 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -4,6 +4,8 @@ PATH view_component (4.0.0.rc1) activesupport (>= 7.1.0, < 8.1) concurrent-ruby (~> 1) + haml (~> 6) + slim (~> 5) temple (~> 0.10) GEM diff --git a/view_component.gemspec b/view_component.gemspec index c1b0a2278..a98232d29 100644 --- a/view_component.gemspec +++ b/view_component.gemspec @@ -35,4 +35,6 @@ Gem::Specification.new do |spec| spec.add_runtime_dependency "activesupport", [">= 7.1.0", "< 8.1"] spec.add_runtime_dependency "concurrent-ruby", "~> 1" spec.add_runtime_dependency "temple", "~> 0.10" + spec.add_runtime_dependency "slim", "~> 5" + spec.add_runtime_dependency "haml", "~> 6" end From 9e93a5abf524e7d47f5e435335ef9f904615ac6d Mon Sep 17 00:00:00 2001 From: reeganviljoen Date: Thu, 3 Jul 2025 20:55:14 +0200 Subject: [PATCH 70/70] add inline erb cache component test --- .../app/components/inline_cache_component.rb | 21 +++++++++++++++++++ test/sandbox/test/rendering_test.rb | 20 ++++++++++++++++++ 2 files changed, 41 insertions(+) create mode 100644 test/sandbox/app/components/inline_cache_component.rb diff --git a/test/sandbox/app/components/inline_cache_component.rb b/test/sandbox/app/components/inline_cache_component.rb new file mode 100644 index 000000000..d7d6a4ea6 --- /dev/null +++ b/test/sandbox/app/components/inline_cache_component.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +class InlineCacheComponent < ViewComponent::Base + include ViewComponent::Cacheable + + cache_on :foo, :bar + + attr_reader :foo, :bar + + def initialize(foo:, bar:) + @foo = foo + @bar = bar + end + + erb_template <<~ERB +

<%= view_cache_dependencies %>

+

"><%= "\#{foo} \#{bar}" %>

+ + <%= render(ButtonToComponent.new) %> + ERB +end diff --git a/test/sandbox/test/rendering_test.rb b/test/sandbox/test/rendering_test.rb index 1dcd215b2..02764bf8f 100644 --- a/test/sandbox/test/rendering_test.rb +++ b/test/sandbox/test/rendering_test.rb @@ -1322,6 +1322,26 @@ def test_around_render assert_text("Hi!") end + def test_inline_cache_component + return if Rails.version < "7.0" + + component = InlineCacheComponent.new(foo: "foo", bar: "bar") + render_inline(component) + + assert_selector(".cache-component__cache-key", text: component.view_cache_dependencies) + assert_selector(".cache-component__cache-message", text: "foo bar") + + render_inline(InlineCacheComponent.new(foo: "foo", bar: "bar")) + + assert_selector(".cache-component__cache-key", text: component.view_cache_dependencies) + + new_component = InlineCacheComponent.new(foo: "foo", bar: "baz") + render_inline(new_component) + + assert_selector(".cache-component__cache-key", text: new_component.view_cache_dependencies) + assert_selector(".cache-component__cache-message", text: "foo baz") + end + def test_cache_component return if Rails.version < "7.0"