-
Notifications
You must be signed in to change notification settings - Fork 448
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add first class component cache #2126
base: main
Are you sure you want to change the base?
Changes from all commits
82459f0
6dd7b90
3f09c7d
39ebb6f
a44ade4
18fdafb
d869441
5e42752
c31701b
a2dcff4
dfb1fad
1223a41
5090a6b
78ac83b
ee10eea
1395282
50d04c1
9c27a32
1631045
10a86bb
bc26709
6afb80b
dce590f
cf55bbb
cad3ce9
1038ee2
e5228e7
50eaded
def21f7
88cb2a6
0d1ef6d
e39fb61
86a1ead
4f4ce4a
e93e8b6
af9017d
9bd4c2d
8dac0dc
471eeb1
bcc6493
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 | ||
<p><%= view_cache_dependencies %></p> | ||
|
||
<p><%= Time.zone.now %>"></p> | ||
<p><%= "#{foo} #{bar}" %></p> | ||
``` | ||
|
||
will result in: | ||
|
||
```html | ||
<p>foo-bar</p> | ||
|
||
<p>2025-03-27 16:46:10 UTC</p> | ||
<p> foo bar</p> | ||
``` |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
# frozen_string_literal: true | ||
|
||
module ViewComponent::Cacheable | ||
extend ActiveSupport::Concern | ||
|
||
included do | ||
class_attribute :__vc_cache_dependencies, default: [:format, :__vc_format] | ||
|
||
# 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? | ||
|
||
__vc_cache_dependencies.filter_map { |dep| send(dep) }.join("-") | ||
end | ||
|
||
# Render component from cache if possible | ||
# | ||
# @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) | ||
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) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We took this for a spin in a few cases of our cached partials and view components that we are familiar with and largely it works for the base cases. This is working to correctly bust a cached VC when it changes or things in the render path 'above' (partials or VCs) it change. What we aren't seeing is when a VC renders another VC or partial as a child that downstream changes of the cached VC pick up changes and handle it. I'm going to fork your branch and offer some test cases based on an approach we use in the vc fragment caching gem (what we're currently solving this with) Effectively we want some way for a child partial or VC change (in either the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @JWShuff like touch: true in rails ? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. also big thanks for testing it, I can look at adding the touch true thing later this week There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @reeganviljoen I'd expect us to test this with changes to child partials. |
||
child.__vc_cache_dependencies = __vc_cache_dependencies.dup | ||
|
||
super | ||
end | ||
end | ||
end |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
<p class='cache-component__cache-key'><%= view_cache_dependencies %></p> | ||
<p class='cache-component__cache-message' data-time=data-time="<%= Time.zone.now %>"><%= "#{foo} #{bar}" %></p> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
# frozen_string_literal: true | ||
|
||
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 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
<p class='cache-component__cache-key'><%= view_cache_dependencies %></p> | ||
|
||
<p class='cache-component__cache-message' data-time=data-time="<%= Time.zone.now %>"><%= "#{foo} #{bar}" %></p> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
# frozen_string_literal: true | ||
|
||
class InheritedCacheComponent < CacheComponent | ||
def initialize(foo:, bar:) | ||
super | ||
end | ||
end |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
<p class='cache-component__cache-key'><%= view_cache_dependencies %></p> | ||
|
||
<p class='cache-component__cache-message' data-time=data-time="<%= Time.zone.now %>"><%= "#{foo} #{bar}" %></p> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 |
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @reeganviljoen I am not a git-wizard, much to my eternal shame, but I set up a fork and adjusted the spec approach here to use the integration examples/controllers to assert the behavior. Feel free to take, leave, or otherwise. There's a wider challenge around partial/template digesting, and on a quiet day I'll port our spec suite over to the VC style and get it implemented so we have all the permutations we know of that need to cache appropriately. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this cache key sufficiently unique? Would this cache the component across changes to the component/sidecar files?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@joelhawksley I don't really understand your question, the cache doesn't really care about how the component renders its content(i.e call, vs sidecar vs normal template vs inline template), it just cares about if the parameters marked for cache are changed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@joelhawksley I have added the variant and format to the cache digest to make sure that it is more unique and that a change to either would generate a unique cache key