class RecordCache::Strategy::IndexCache

Public Class Methods

new(base, attribute, record_store, options) click to toggle source
Calls superclass method RecordCache::Strategy::Base::new
# File lib/record_cache/strategy/index_cache.rb, line 20
def initialize(base, attribute, record_store, options)
  super
  @cache_key_prefix << "#{attribute}="
end
parse(base, record_store, options) click to toggle source

parse the options and return (an array of) instances of this strategy

# File lib/record_cache/strategy/index_cache.rb, line 6
def self.parse(base, record_store, options)
  return nil unless options[:index]
  return nil unless base.table_exists?
  
  raise "Index cache '#{options[:index].inspect}' on #{base.name} is redundant as index cache queries are handled by the full table cache." if options[:full_table]
  raise ":index => #{options[:index].inspect} option cannot be used unless 'id' is present on #{base.name}" unless base.columns_hash['id']
  [options[:index]].flatten.compact.map do |attribute|
    type = base.columns_hash[attribute.to_s].try(:type)
    raise "No column found for index '#{attribute}' on #{base.name}." unless type
    raise "Incorrect type (expected integer, found #{type}) for index '#{attribute}' on #{base.name}." unless type == :integer
    IndexCache.new(base, attribute, record_store, options)
  end
end

Public Instance Methods

cacheable?(query) click to toggle source

Can the cache retrieve the records based on this query?

# File lib/record_cache/strategy/index_cache.rb, line 26
def cacheable?(query)
  # allow limit of 1 for has_one
  query.where_value(@attribute) && (query.limit.nil? || (query.limit == 1 && !query.sorted?))
end
record_change(record, action) click to toggle source

Handle create/update/destroy (use record.previous_changes to find the old values in case of an update)

# File lib/record_cache/strategy/index_cache.rb, line 32
def record_change(record, action)
  if action == :destroy
    remove_from_index(record.send(@attribute), record.id)
  elsif action == :create
    add_to_index(record.send(@attribute), record.id)
  else
    index_change = record.previous_changes[@attribute.to_s] || record.previous_changes[@attribute]
    return unless index_change
    remove_from_index(index_change[0], record.id)
    add_to_index(index_change[1], record.id)
  end
end

Protected Instance Methods

fetch_records(query) click to toggle source

retrieve the record(s) based on the given query

# File lib/record_cache/strategy/index_cache.rb, line 48
def fetch_records(query)
  value = query.where_value(@attribute)
  # make sure CacheCase.filter! does not see this where clause anymore
  query.wheres.delete(@attribute)
  # retrieve the cache key for this index and value
  key = cache_key(value)
  # retrieve the current version of the ids list
  current_version = version_store.current(key)
  # create the versioned key, renew the version in case it was missing in the version store
  versioned_key = versioned_key(key, current_version || version_store.renew_for_read(key, version_opts))
  # retrieve the ids from the local cache based on the current version from the version store
  ids = current_version ? fetch_ids_from_cache(versioned_key) : nil
  # logging (only in debug mode!) and statistics
  log_cache_hit(versioned_key, ids) if RecordCache::Base.logger.debug?
  statistics.add(1, ids ? 1 : 0) if statistics.active?
  # retrieve the ids from the DB if the result was not fresh
  ids = fetch_ids_from_db(versioned_key, value) unless ids
  # use the IdCache to retrieve the records based on the ids
  @base.record_cache[:id].send(:fetch_records, ::RecordCache::Query.new({:id => ids}))
end

Private Instance Methods

add_to_index(value, id) click to toggle source

add one record(id) to the index with the given value

# File lib/record_cache/strategy/index_cache.rb, line 92
def add_to_index(value, id)
  renew_version(value.to_i) { |ids| ids << id } if value
end
fetch_ids_from_cache(versioned_key) click to toggle source

Retrieve the ids from the local cache

# File lib/record_cache/strategy/index_cache.rb, line 74
def fetch_ids_from_cache(versioned_key)
  record_store.read(versioned_key)
end
fetch_ids_from_db(versioned_key, value) click to toggle source

retrieve the ids from the database and update the local cache

# File lib/record_cache/strategy/index_cache.rb, line 79
def fetch_ids_from_db(versioned_key, value)
  RecordCache::Base.without_record_cache do
    # go straight to SQL result for optimal performance
    sql = @base.select('id').where(@attribute => value).to_sql
    ids = []; @base.connection.execute(sql).each{ |row| ids << (row.is_a?(Hash) ? row['id'] : row.first).to_i }
    record_store.write(versioned_key, ids)
    ids
  end
end
log_cache_hit(key, ids) click to toggle source

log cache hit/miss to debug log

# File lib/record_cache/strategy/index_cache.rb, line 119
def log_cache_hit(key, ids)
  RecordCache::Base.logger.debug{ "IndexCache #{ids ? 'hit' : 'miss'} for #{key}: found #{ids ? ids.size : 'no'} ids" }
end
remove_from_index(value, id) click to toggle source

remove one record(id) from the index with the given value

# File lib/record_cache/strategy/index_cache.rb, line 97
def remove_from_index(value, id)
  renew_version(value.to_i) { |ids| ids.delete(id) } if value
end
renew_version(value) { |ids| ... } click to toggle source

renew the version store and update the local store

# File lib/record_cache/strategy/index_cache.rb, line 102
def renew_version(value, &block)
  # retrieve local version and increment version store
  key = cache_key(value)
  old_version = version_store.current(key)
  new_version = version_store.renew(key, true, version_opts)
  # try to update the ids list based on the last version
  ids = fetch_ids_from_cache(versioned_key(key, old_version))
  if ids
    ids = Array.new(ids)
    yield ids
    record_store.write(versioned_key(key, new_version), ids)
  end
end