aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord/lib/active_record/relation.rb
diff options
context:
space:
mode:
authorLachlan Sylvester <lachlan.sylvester@hypothetical.com.au>2018-11-05 11:52:33 +1100
committerKasper Timm Hansen <kaspth@gmail.com>2019-04-16 22:54:15 +0200
commit4f2ac80d4cdb01c4d3c1765637bed76cc91c1e35 (patch)
tree130aef88d29cd5a18535ce5123b54364ea90a13c /activerecord/lib/active_record/relation.rb
parent758ba117a008b6ea2d3b92c53b6a7a8d7ccbca69 (diff)
downloadrails-4f2ac80d4cdb01c4d3c1765637bed76cc91c1e35.tar.gz
rails-4f2ac80d4cdb01c4d3c1765637bed76cc91c1e35.tar.bz2
rails-4f2ac80d4cdb01c4d3c1765637bed76cc91c1e35.zip
Add collection cache versioning
Cache versioning enables the same cache key to be reused when the object being cached changes by moving the volatile part of the cache key out of the cache key and into a version that is embedded in the cache entry. This is already occurring when the object being cached is an `ActiveRecord::Base`, but when caching an `ActiveRecord::Relation` we are currently still putting the volatile information (max updated at and count) as part of the cache key. This PR moves the volatile part of the relations `cache_key` into the `cache_version` to support recycling cache keys for `ActiveRecord::Relation`s.
Diffstat (limited to 'activerecord/lib/active_record/relation.rb')
-rw-r--r--activerecord/lib/active_record/relation.rb51
1 files changed, 36 insertions, 15 deletions
diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb
index cd62b0b881..8eb71e6454 100644
--- a/activerecord/lib/active_record/relation.rb
+++ b/activerecord/lib/active_record/relation.rb
@@ -291,27 +291,23 @@ module ActiveRecord
limit_value ? records.many? : size > 1
end
- # Returns a cache key that can be used to identify the records fetched by
- # this query. The cache key is built with a fingerprint of the sql query,
- # the number of records matched by the query and a timestamp of the last
- # updated record. When a new record comes to match the query, or any of
- # the existing records is updated or deleted, the cache key changes.
+ # Returns a stable cache key that can be used to identify this query.
+ # The cache key is built with a fingerprint of the SQL query.
#
- # Product.where("name like ?", "%Cosmic Encounter%").cache_key
- # # => "products/query-1850ab3d302391b85b8693e941286659-1-20150714212553907087000"
+ # Product.where("name like ?", "%Cosmic Encounter%").cache_key
+ # # => "products/query-1850ab3d302391b85b8693e941286659"
#
- # If the collection is loaded, the method will iterate through the records
- # to generate the timestamp, otherwise it will trigger one SQL query like:
+ # If ActiveRecord::Base.collection_cache_versioning is turned off, as it was
+ # in Rails 6.0 and earlier, the cache key will also include a version.
#
- # SELECT COUNT(*), MAX("products"."updated_at") FROM "products" WHERE (name like '%Cosmic Encounter%')
+ # ActiveRecord::Base.collection_cache_versioning = false
+ # Product.where("name like ?", "%Cosmic Encounter%").cache_key
+ # # => "products/query-1850ab3d302391b85b8693e941286659-1-20150714212553907087000"
#
# You can also pass a custom timestamp column to fetch the timestamp of the
# last updated record.
#
# Product.where("name like ?", "%Game%").cache_key(:last_reviewed_at)
- #
- # You can customize the strategy to generate the key on a per model basis
- # overriding ActiveRecord::Base#collection_cache_key.
def cache_key(timestamp_column = :updated_at)
@cache_keys ||= {}
@cache_keys[timestamp_column] ||= @klass.collection_cache_key(self, timestamp_column)
@@ -321,6 +317,31 @@ module ActiveRecord
query_signature = ActiveSupport::Digest.hexdigest(to_sql)
key = "#{klass.model_name.cache_key}/query-#{query_signature}"
+ if cache_version(timestamp_column)
+ key
+ else
+ "#{key}-#{compute_cache_version(timestamp_column)}"
+ end
+ end
+
+ # Returns a cache version that can be used together with the cache key to form
+ # a recyclable caching scheme. The cache version is built with the number of records
+ # matching the query, and the timestamp of the last updated record. When a new record
+ # comes to match the query, or any of the existing records is updated or deleted,
+ # the cache version changes.
+ #
+ # If the collection is loaded, the method will iterate through the records
+ # to generate the timestamp, otherwise it will trigger one SQL query like:
+ #
+ # SELECT COUNT(*), MAX("products"."updated_at") FROM "products" WHERE (name like '%Cosmic Encounter%')
+ def cache_version(timestamp_column = :updated_at)
+ if collection_cache_versioning
+ @cache_versions ||= {}
+ @cache_versions[timestamp_column] ||= compute_cache_version(timestamp_column)
+ end
+ end
+
+ def compute_cache_version(timestamp_column) # :nodoc:
if loaded? || distinct_value
size = records.size
if size > 0
@@ -356,9 +377,9 @@ module ActiveRecord
end
if timestamp
- "#{key}-#{size}-#{timestamp.utc.to_s(cache_timestamp_format)}"
+ "#{size}-#{timestamp.utc.to_s(cache_timestamp_format)}"
else
- "#{key}-#{size}"
+ "#{size}"
end
end