Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
tree: 2856eb8bbd
Fetching contributors…

Cannot retrieve contributors at this time

275 lines (212 sloc) 6.926 kb
$TESTING_CM = defined? $TESTING_CM
require 'timeout'
require 'memcache_util' unless $TESTING_CM
require 'active_record' unless $TESTING_CM
##
# An abstract ActiveRecord descendant that caches records in memcache and in
# local memory.
#
# CachedModel can store into both a local in-memory cache and in memcached.
# By default memcached is enabled and the local cache is disabled.
#
# Local cache use can be enabled or disabled with
# CachedModel::use_local_cache=. If you do enable the local cache be sure to
# add a before filter that calls CachedModel::cache_reset for every request.
#
# memcached use can be enabled or disabled with CachedModel::use_memcache=.
#
# You can adjust the memcached TTL with CachedModel::ttl=
class CachedModel < ActiveRecord::Base
self.abstract_class = true
VERSION = '1.3.2'
@cache_delay_commit = {}
@cache_local = {}
@cache_transaction_level = 0
@use_local_cache = false
@use_memcache = true
@ttl = 60 * 15
class << self
# :stopdoc:
##
# The transaction commit buffer. You shouldn't touch me.
attr_accessor :cache_delay_commit
##
# The local process cache. You shouldn't touch me.
attr_reader :cache_local
##
# The transaction nesting level. You shouldn't touch me.
attr_accessor :cache_transaction_level
# :startdoc:
##
# Enables or disables use of the local cache.
#
# NOTE if you enable this you must call #cache_reset or you will
# experience uncontrollable process growth!
#
# Defaults to false.
attr_writer :use_local_cache
##
# Enables or disables the use of memcache.
attr_writer :use_memcache
##
# Memcache record time-to-live for stored records.
attr_accessor :ttl
end
##
# Invalidate the cache entry for a record. The update method will
# automatically invalidate the cache when updates are made through
# ActiveRecord model record. However, several methods update tables with
# direct sql queries for effeciency. These methods should call this method
# to invalidate the cache after making those changes.
#
# NOTE - if a SQL query updates multiple rows with one query, there is
# currently no way to invalidate the affected entries unless the entire
# cache is dumped or until the TTL expires, so try not to do this.
def self.cache_delete(klass, id)
key = "#{klass}:#{id}"
CachedModel.cache_local.delete key if CachedModel.use_local_cache?
Cache.delete "active_record:#{key}" if CachedModel.use_memcache?
end
##
# Invalidate the local process cache. This should be called from a before
# filter at the beginning of each request.
def self.cache_reset
CachedModel.cache_local.clear if CachedModel.use_local_cache?
end
##
# Override the find method to look for values in the cache before going to
# the database.
#--
# TODO Push a bunch of code down into find_by_sql where it really should
# belong.
def self.find(*args)
args[0] = args.first.to_i if args.first =~ /\A\d+\Z/
# Only handle simple find requests. If the request was more complicated,
# let the base class handle it, but store the retrieved records in the
# local cache in case we need them later.
if args.length != 1 or not Fixnum === args.first then
# Rails requires multiple levels of indirection to look up a record
# First call super
records = super
# Then, if it was a :all, just return
return records if args.first == :all
return records if RAILS_ENV == 'test'
case records
when Array then
records.each { |r| r.cache_store }
end
return records
end
return super
end
##
# Find by primary key from the cache.
def self.find_by_sql(*args)
return super unless args.first =~ /^SELECT \* FROM #{table_name} WHERE \(#{table_name}\.#{primary_key} = '?(\d+)'?\)( +LIMIT 1|\Z)/
id = $1.to_i
# Try to find the record in the local cache.
cache_key_local = "#{name}:#{id}"
if CachedModel.use_local_cache? then
record = CachedModel.cache_local[cache_key_local]
return [record] unless record.nil?
end
# Try to find the record in memcache and add it to the local cache
if CachedModel.use_memcache? then
record = Cache.get "active_record:#{cache_key_local}"
unless record.nil? then
if CachedModel.use_local_cache? then
CachedModel.cache_local[cache_key_local] = record
end
return [record]
end
end
# Fetch the record from the DB
records = super
records.first.cache_store unless records.empty? # only one
return records
end
##
# Delay updating the cache while in a transaction.
def self.transaction(*args)
level = CachedModel.cache_transaction_level += 1
CachedModel.cache_delay_commit[level] = []
value = super
waiting = CachedModel.cache_delay_commit.delete level
waiting.each do |obj| obj.cache_store end
return value
ensure
CachedModel.cache_transaction_level -= 1
end
##
# Returns true if use of the local cache is enabled.
def self.use_local_cache?
return @use_local_cache
end
##
# Returns true if use of memcache is enabled.
def self.use_memcache?
return @use_memcache
end
##
# Delete the entry from the cache now that it isn't in the DB.
def destroy
return super
ensure
cache_delete
end
##
# Invalidate the cache for this record before reloading from the DB.
def reload
cache_delete
return super
ensure
cache_store
end
##
# Store a new copy of ourselves into the cache.
def update
return super
ensure
cache_store
end
##
# Remove this record from the cache.
def cache_delete
cache_local.delete cache_key_local if CachedModel.use_local_cache?
Cache.delete cache_key_memcache if CachedModel.use_memcache?
end
##
# The local cache key for this record.
def cache_key_local
return "#{self.class}:#{id}"
end
##
# The memcache key for this record.
def cache_key_memcache
return "active_record:#{cache_key_local}"
end
##
# The local object cache.
def cache_local
return CachedModel.cache_local
end
##
# Store this record in the cache without associations. Storing associations
# leads to wasted cache space and hard-to-debug problems.
def cache_store
obj = dup
obj.send :instance_variable_set, :@attributes, attributes_before_type_cast
transaction_level = CachedModel.cache_transaction_level
if CachedModel.cache_delay_commit[transaction_level].nil? then
if CachedModel.use_local_cache? then
cache_local[cache_key_local] = obj
end
if CachedModel.use_memcache? then
Cache.put cache_key_memcache, obj, CachedModel.ttl
end
else
CachedModel.cache_delay_commit[transaction_level] << obj
end
nil
end
end
Jump to Line
Something went wrong with that request. Please try again.