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.



17
18
19
# File 'lib/cached_model.rb', line 17

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.



50
51
52
53
54
# File 'lib/cached_model.rb', line 50

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

.cache_resetObject

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



60
61
62
# File 'lib/cached_model.rb', line 60

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.



29
30
31
32
33
34
35
36
37
# File 'lib/cached_model.rb', line 29

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)


22
23
24
# File 'lib/cached_model.rb', line 22

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.



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
107
108
109
110
111
112
113
114
# File 'lib/cached_model.rb', line 68

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



121
122
123
124
125
126
127
128
129
# File 'lib/cached_model.rb', line 121

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

  return super
end

Instance Method Details

#cache_deleteObject

Remove this record from the cache.



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

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.



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

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

#cache_key_memcacheObject

The memcache key for this record.



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

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

#cache_localObject

The local object cache.



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

def cache_local
  return CachedModel.cache_local
end

#cache_storeObject

Store this record in the cache without associations. Storing associations leads to wasted cache space and hard-to-debug problems.



192
193
194
195
196
197
# File 'lib/cached_model.rb', line 192

def cache_store
  obj = dup
  obj.send :instance_variable_set, :@attributes, attributes_before_type_cast
  cache_local[cache_key_local] = obj
  Cache.put cache_key_memcache, obj, TTL
end

#destroyObject

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



134
135
136
137
138
# File 'lib/cached_model.rb', line 134

def destroy
  return super
ensure
  cache_delete
end

#reloadObject

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



143
144
145
146
147
148
# File 'lib/cached_model.rb', line 143

def reload
  cache_delete
  return super
ensure
  cache_store
end

#updateObject

Store a new copy of ourselves into the cache.



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

def update
  return super
ensure
  cache_store
end