diff options
Diffstat (limited to 'activerecord/lib/active_record/integration.rb')
-rw-r--r-- | activerecord/lib/active_record/integration.rb | 82 |
1 files changed, 67 insertions, 15 deletions
diff --git a/activerecord/lib/active_record/integration.rb b/activerecord/lib/active_record/integration.rb index 33c4066b89..4a97061731 100644 --- a/activerecord/lib/active_record/integration.rb +++ b/activerecord/lib/active_record/integration.rb @@ -22,6 +22,14 @@ module ActiveRecord # # This is +true+, by default on Rails 5.2 and above. class_attribute :cache_versioning, instance_writer: false, default: false + + ## + # :singleton-method: + # Indicates whether to use a stable #cache_key method that is accompanied + # by a changing version in the #cache_version method on collections. + # + # This is +false+, by default until Rails 6.1. + class_attribute :collection_cache_versioning, instance_writer: false, default: false end # Returns a +String+, which Action Pack uses for constructing a URL to this @@ -61,23 +69,14 @@ module ActiveRecord # # Product.cache_versioning = false # Product.find(5).cache_key # => "products/5-20071224150000" (updated_at available) - def cache_key(*timestamp_names) + def cache_key if new_record? "#{model_name.cache_key}/new" else - if cache_version && timestamp_names.none? + if cache_version "#{model_name.cache_key}/#{id}" else - timestamp = if timestamp_names.any? - ActiveSupport::Deprecation.warn(<<-MSG.squish) - Specifying a timestamp name for #cache_key has been deprecated in favor of - the explicit #cache_version method that can be overwritten. - MSG - - max_updated_column_timestamp(timestamp_names) - else - max_updated_column_timestamp - end + timestamp = max_updated_column_timestamp if timestamp timestamp = timestamp.utc.to_s(cache_timestamp_format) @@ -94,10 +93,21 @@ module ActiveRecord # 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). + # +false+. def cache_version - if cache_versioning && timestamp = try(:updated_at) - timestamp.utc.to_s(:usec) + return unless cache_versioning + + if has_attribute?("updated_at") + timestamp = updated_at_before_type_cast + if can_use_fast_cache_version?(timestamp) + raw_timestamp_to_cache_version(timestamp) + elsif timestamp = updated_at + timestamp.utc.to_s(cache_timestamp_format) + end + else + if self.class.has_attribute?("updated_at") + raise ActiveModel::MissingAttributeError, "missing attribute: updated_at" + end end end @@ -150,6 +160,48 @@ module ActiveRecord end end end + + def collection_cache_key(collection = all, timestamp_column = :updated_at) # :nodoc: + collection.send(:compute_cache_key, timestamp_column) + end end + + private + # Detects if the value before type cast + # can be used to generate a cache_version. + # + # The fast cache version only works with a + # string value directly from the database. + # + # We also must check if the timestamp format has been changed + # or if the timezone is not set to UTC then + # we cannot apply our transformations correctly. + def can_use_fast_cache_version?(timestamp) + timestamp.is_a?(String) && + cache_timestamp_format == :usec && + default_timezone == :utc && + !updated_at_came_from_user? + end + + # Converts a raw database string to `:usec` + # format. + # + # Example: + # + # timestamp = "2018-10-15 20:02:15.266505" + # raw_timestamp_to_cache_version(timestamp) + # # => "20181015200215266505" + # + # PostgreSQL truncates trailing zeros, + # https://github.com/postgres/postgres/commit/3e1beda2cde3495f41290e1ece5d544525810214 + # to account for this we pad the output with zeros + def raw_timestamp_to_cache_version(timestamp) + key = timestamp.delete("- :.") + if key.length < 20 + key.ljust(20, "0") + else + key + end + end end end |