module ActiveRecord::Integration

Public Class Methods

cache_timestamp_format() click to toggle source

Indicates the format used to generate the timestamp in the cache key, if versioning is off. Accepts any of the symbols in Time::DATE_FORMATS.

This is :usec, by default.

# File lib/active_record/integration.rb, line 16
class_attribute :cache_timestamp_format, instance_writer: false, default: :usec
cache_versioning() click to toggle source

Indicates whether to use a stable cache_key method that is accompanied by a changing version in the cache_version method.

This is true, by default on Rails 5.2 and above.

# File lib/active_record/integration.rb, line 24
class_attribute :cache_versioning, instance_writer: false, default: false
collection_cache_versioning() click to toggle source

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.

# File lib/active_record/integration.rb, line 32
class_attribute :collection_cache_versioning, instance_writer: false, default: false

Public Instance Methods

cache_key() click to toggle source

Returns a stable cache key that can be used to identify this record.

Product.new.cache_key     # => "products/new"
Product.find(5).cache_key # => "products/5"

If ActiveRecord::Base.cache_versioning is turned off, as it was in Rails 5.1 and earlier, the cache key will also include a version.

Product.cache_versioning = false
Product.find(5).cache_key  # => "products/5-20071224150000" (updated_at available)
# File lib/active_record/integration.rb, line 72
def cache_key
  if new_record?
    "#{model_name.cache_key}/new"
  else
    if cache_version
      "#{model_name.cache_key}/#{id}"
    else
      timestamp = max_updated_column_timestamp

      if timestamp
        timestamp = timestamp.utc.to_fs(cache_timestamp_format)
        "#{model_name.cache_key}/#{id}-#{timestamp}"
      else
        "#{model_name.cache_key}/#{id}"
      end
    end
  end
end
cache_key_with_version() click to toggle source

Returns a cache key along with the version.

# File lib/active_record/integration.rb, line 114
def cache_key_with_version
  if version = cache_version
    "#{cache_key}-#{version}"
  else
    cache_key
  end
end
cache_version() click to toggle source

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.

# File lib/active_record/integration.rb, line 97
def cache_version
  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_fs(cache_timestamp_format)
    end
  elsif self.class.has_attribute?("updated_at")
    raise ActiveModel::MissingAttributeError, "missing attribute 'updated_at' for #{self.class}"
  end
end
to_param() click to toggle source

Returns a String, which Action Pack uses for constructing a URL to this object. The default implementation returns this record’s id as a String, or nil if this record’s unsaved.

For example, suppose that you have a User model, and that you have a resources :users route. Normally, user_path will construct a path with the user object’s ‘id’ in it:

user = User.find_by(name: 'Phusion')
user_path(user)  # => "/users/1"

You can override to_param in your model to make user_path construct a path using the user’s name instead of the user’s id:

class User < ActiveRecord::Base
  def to_param  # overridden
    name
  end
end

user = User.find_by(name: 'Phusion')
user_path(user)  # => "/users/Phusion"
# File lib/active_record/integration.rb, line 57
def to_param
  return unless id
  Array(id).join(self.class.param_delimiter)
end

Private Instance Methods

can_use_fast_cache_version?(timestamp) click to toggle source

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.

# File lib/active_record/integration.rb, line 178
def can_use_fast_cache_version?(timestamp)
  timestamp.is_a?(String) &&
    cache_timestamp_format == :usec &&
    # FIXME: checking out a connection for this is wasteful
    # we should store/cache this information in the schema cache
    # or similar.
    self.class.with_connection(&:default_timezone) == :utc &&
    !updated_at_came_from_user?
end
raw_timestamp_to_cache_version(timestamp) click to toggle source

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, github.com/postgres/postgres/commit/3e1beda2cde3495f41290e1ece5d544525810214 to account for this we pad the output with zeros

# File lib/active_record/integration.rb, line 200
def raw_timestamp_to_cache_version(timestamp)
  key = timestamp.delete("- :.")
  if key.length < 20
    key.ljust(20, "0")
  else
    key
  end
end