Class: CachedModel

Inherits:
ActiveRecord::Base
  • Object
show all
Defined in:
lib/cached_model.rb

Overview

An abstract ActiveRecord descendant that caches records in memcache and in local memory.

Constant Summary collapse

KEY =
'active_record'
TTL =

seconds

60 * 15

Class Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Class Attribute Details

.cache_localObject (readonly)

Returns the value of attribute cache_local.



14
15
16
# File 'lib/cached_model.rb', line 14

def cache_local
  @cache_local
end

Class Method Details

.cache_delete(klass, id) ⇒ Object

Invalidate the cache entry for an 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.



47
48
49
50
51
# File 'lib/cached_model.rb', line 47

def self.cache_delete(klass, id)
  key = "#{klass}:#{id}"
  CachedModel.cache_local.delete key
  Cache.delete "#{KEY}:#{key}"
end

.cache_resetObject

Invalidate the per-request cache. This should be called from a before filter at the beginning of each request.



57
58
59
# File 'lib/cached_model.rb', line 57

def self.cache_reset
  CachedModel.cache_local.clear
end

.class_name_of_active_record_descendant(klass) ⇒ Object

Override the flawed assumption ActiveRecord::Base makes about inheritance.



26
27
28
29
30
31
32
33
34
# File 'lib/cached_model.rb', line 26

def self.class_name_of_active_record_descendant(klass)
  if klass.superclass == CachedModel then
    return klass.name
  elsif klass.superclass.nil? then
    raise ActiveRecordError, "#{name} doesn't descend from ActiveRecord::Base"
  else
    class_name_of_active_record_descendant klass.superclass
  end
end

.descends_from_active_record?Boolean

Override the flawed assumption ActiveRecord::Base makes about inheritance.

Returns:

  • (Boolean)


19
20
21
# File 'lib/cached_model.rb', line 19

def self.descends_from_active_record?
  superclass == CachedModel
end

.find(*args) ⇒ Object

Override the find method to look for values in the cache before going to the database.



65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
# File 'lib/cached_model.rb', line 65

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 $TESTING or args.length != 1 or not Fixnum === args.first then
    records = super
    if RAILS_ENV != 'test' and Array === records then
      records.each { |r| r.cache_store }
    end
    return records
  end

  # Try to find the record in the local cache.
  id = args.first
  cache_key_local = "#{name}:#{id}"
  record = CachedModel.cache_local[cache_key_local]
  return record unless record.nil?

  # Try to find the record in memcache and add it to the local cache
  record = Cache.get "#{KEY}:#{cache_key_local}"
  unless record.nil? then
    CachedModel.cache_local[cache_key_local] = record
    return record
  end

  # Fetch the record from the DB and cache it.
  #
  # We don't want the subsequent find_by_sql to loop back here, so guard
  # the call.
  #
  # NOTE - this guard isn't thread safe.
  begin
    @skip_find_hack = true
    record = super(args).first
    record.cache_store
  ensure
    @skip_find_hack = false
  end

  return record
end

.find_by_sql(*args) ⇒ Object

Skip the special handling for find by primary key if this method was called from find. If this is really a lookup for a single row by primary key, use a simple find call instead.



113
114
115
116
117
118
119
120
121
# File 'lib/cached_model.rb', line 113

def self.find_by_sql(*args)
  unless @skip_find_hack or $TESTING then
    if args.first =~ /SELECT \* FROM #{table_name} WHERE \(#{table_name}\.#{primary_key} = '?(\d+)'?\) LIMIT 1/ then
      return [self.find($1.to_i)]
    end
  end

  return super
end

Instance Method Details

#cache_deleteObject

Remove this record from the cache.



170
171
172
173
# File 'lib/cached_model.rb', line 170

def cache_delete
  cache_local.delete cache_key_local
  Cache.delete cache_key_memcache
end

#cache_key_localObject

The local cache key for this record.



178
179
180
# File 'lib/cached_model.rb', line 178

def cache_key_local
  return "#{self.class.name}:#{self.id}"
end

#cache_key_memcacheObject

The memcache key for this record.



185
186
187
# File 'lib/cached_model.rb', line 185

def cache_key_memcache
  return "#{KEY}:#{cache_key_local}"
end

#cache_localObject

The local object cache.



155
156
157
# File 'lib/cached_model.rb', line 155

def cache_local
  return CachedModel.cache_local
end

#cache_storeObject

Store this record in the cache.



162
163
164
165
# File 'lib/cached_model.rb', line 162

def cache_store
  cache_local[cache_key_memcache] = self
  Cache.put cache_key_memcache, self, TTL
end

#destroyObject

Delete the entry from the cache now that it isn’t in the DB.



138
139
140
141
142
# File 'lib/cached_model.rb', line 138

def destroy
  return super
ensure
  cache_delete
end

#reloadObject

Invalidate the cache for this record before reloading from the DB.



147
148
149
150
# File 'lib/cached_model.rb', line 147

def reload
  cache_delete
  return super
end

#updateObject

Delete the entry from the cache so that the next call goes to the database for the freshest copy of the record. This will also ensure that if for some reason a stale copy of the record was cached we can get rid of it.



128
129
130
131
132
133
# File 'lib/cached_model.rb', line 128

def update
  cache_delete
  val = super
  cache_store
  return val
end