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
Recyclable cache keys #29092
Recyclable cache keys #29092
Changes from 25 commits
97c9788
38eb185
c0c6033
5fe31e6
5dc8d62
07c7b13
4cd887c
fdcd89f
b074458
25b22a9
863c3c1
86adbeb
542d661
b10d84c
3163833
b23e63e
cff4c86
755d09d
ac02e73
43296f6
8f2a601
2275c34
49392d0
82dbfc6
c564d39
7383f24
fe198fa
1dd2351
0aa2ea5
60785ec
ac3a69f
627aca6
f20aac8
f3ef10d
6907fc5
4328620
8f1d36f
5676849
24d47e7
8171730
2a5d9b4
3490306
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 |
---|---|---|
|
@@ -26,10 +26,6 @@ def setup | |
@controller.request = @request | ||
@controller.response = @response | ||
end | ||
|
||
def test_fragment_cache_key | ||
assert_equal "views/what a key", @controller.fragment_cache_key("what a key") | ||
end | ||
end | ||
|
||
class CachingController < ActionController::Base | ||
|
@@ -43,6 +39,8 @@ def some_action; end | |
end | ||
|
||
class FragmentCachingTest < ActionController::TestCase | ||
ModelWithKeyAndVersion = Struct.new(:cache_key, :cache_version) | ||
|
||
def setup | ||
super | ||
@store = ActiveSupport::Cache::MemoryStore.new | ||
|
@@ -53,12 +51,17 @@ def setup | |
@controller.params = @params | ||
@controller.request = @request | ||
@controller.response = @response | ||
|
||
@m1v1 = ModelWithKeyAndVersion.new("model/1", "1") | ||
@m1v2 = ModelWithKeyAndVersion.new("model/1", "2") | ||
@m2v1 = ModelWithKeyAndVersion.new("model/2", "1") | ||
@m2v2 = ModelWithKeyAndVersion.new("model/2", "2") | ||
end | ||
|
||
def test_fragment_cache_key | ||
assert_equal "views/what a key", @controller.fragment_cache_key("what a key") | ||
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. Maybe keep the tests for 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. Yeah, good idea. |
||
assert_equal "views/test.host/fragment_caching_test/some_action", | ||
@controller.fragment_cache_key(controller: "fragment_caching_test", action: "some_action") | ||
assert_equal [ :views, "what a key" ], @controller.combined_fragment_cache_key("what a key") | ||
assert_equal [ :views, "test.host/fragment_caching_test/some_action" ], | ||
@controller.combined_fragment_cache_key(controller: "fragment_caching_test", action: "some_action") | ||
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. Need tests for |
||
end | ||
|
||
def test_read_fragment_with_caching_enabled | ||
|
@@ -72,6 +75,12 @@ def test_read_fragment_with_caching_disabled | |
assert_nil @controller.read_fragment("name") | ||
end | ||
|
||
def test_read_fragment_with_versioned_model | ||
@controller.write_fragment([ "stuff", @m1v1 ], "hello") | ||
assert_equal "hello", @controller.read_fragment([ "stuff", @m1v1 ]) | ||
assert_nil @controller.read_fragment([ "stuff", @m1v2 ]) | ||
end | ||
|
||
def test_fragment_exist_with_caching_enabled | ||
@store.write("views/name", "value") | ||
assert @controller.fragment_exist?("name") | ||
|
@@ -472,9 +481,9 @@ def setup | |
|
||
def test_fragment_cache_key | ||
@controller.account_id = "123" | ||
assert_equal "views/v1/123/what a key", @controller.fragment_cache_key("what a key") | ||
assert_equal [ :views, "v1", "123", "what a key" ], @controller.combined_fragment_cache_key("what a key") | ||
|
||
@controller.account_id = nil | ||
assert_equal "views/v1//what a key", @controller.fragment_cache_key("what a key") | ||
assert_equal [ :views, "v1", "what a key" ], @controller.combined_fragment_cache_key("what a key") | ||
end | ||
end |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -13,6 +13,15 @@ module Integration | |
# This is +:usec+, by default. | ||
class_attribute :cache_timestamp_format, instance_writer: false | ||
self.cache_timestamp_format = :usec | ||
|
||
## | ||
# :singleton-method: | ||
# Indicates whether to use a stable #cache_key method that is accompanied | ||
# by a changing version in the #cache_version method. | ||
# | ||
# This is +false+, by default until Rails 6.0. | ||
class_attribute :cache_versioning, instance_writer: false | ||
self.cache_versioning = false | ||
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. Do we want new 5.2 applications to use this as true by default? 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. Yes, @matthewd has a checklist of what's needed to switch over. |
||
end | ||
|
||
# Returns a +String+, which Action Pack uses for constructing a URL to this | ||
|
@@ -52,25 +61,47 @@ def to_param | |
# used to generate the key: | ||
# | ||
# Person.find(5).cache_key(:updated_at, :last_reviewed_at) | ||
# | ||
# If ActiveRecord::Base.cache_versioning is turned on, no version will be included | ||
# in the cache key. The version will instead be supplied by #cache_version. This | ||
# separation enables recycling of cache keys. | ||
# | ||
# Product.cache_versioning = true | ||
# Product.new.cache_key # => "products/new" | ||
# Person.find(5).cache_key # => "people/5" (even if updated_at available) | ||
def cache_key(*timestamp_names) | ||
if new_record? | ||
"#{model_name.cache_key}/new" | ||
else | ||
timestamp = if timestamp_names.any? | ||
max_updated_column_timestamp(timestamp_names) | ||
if cache_version && timestamp_names.none? | ||
"#{model_name.cache_key}/#{id}" | ||
else | ||
max_updated_column_timestamp | ||
end | ||
timestamp = if timestamp_names.any? | ||
max_updated_column_timestamp(timestamp_names) | ||
else | ||
max_updated_column_timestamp | ||
end | ||
|
||
if timestamp | ||
timestamp = timestamp.utc.to_s(cache_timestamp_format) | ||
"#{model_name.cache_key}/#{id}-#{timestamp}" | ||
else | ||
"#{model_name.cache_key}/#{id}" | ||
if timestamp | ||
timestamp = timestamp.utc.to_s(cache_timestamp_format) | ||
"#{model_name.cache_key}/#{id}-#{timestamp}" | ||
else | ||
"#{model_name.cache_key}/#{id}" | ||
end | ||
end | ||
end | ||
end | ||
|
||
# Returns a cache version that can be used together with the cache key to form | ||
# a recyclable caching scheme. By default, the #updated_at column is used for the | ||
# cache_version, but this method can be overwritten to return something else. | ||
# | ||
# Note, this method will return nil if ActiveRecord::Base.cache_versioning is set to | ||
# +false+ (which it is by default until Rails 6.0). | ||
def cache_version | ||
try(:updated_at).try(:to_i) if cache_versioning | ||
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. As we convert all versions to string at the low level, it makes sense to call |
||
end | ||
|
||
module ClassMethods | ||
# Defines your model's +to_param+ method to generate "pretty" URLs | ||
# using +method_name+, which can be any attribute or method that | ||
|
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.
RAILS_CACHE_ID
var is weird: currently it only applies to caches made through actionpack, but not directly atRails.cache
.The current problem with that can be here because you add
RAILS_CACHE_ID
topayload[:key]
explicitly now and then expand it with same ENV var prefix again.I think it is a bug and all direct calls to
Rails.cache
should also incorporateRAILS_CACHE_ID
.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.
Agree. I'd rather make it a direct setting for Rails.cache, but that's for a later PR.