Class: ActiveRecord::Base
- Inherits:
-
Object
- Object
- ActiveRecord::Base
- Defined in:
- lib/cs_active_record.rb
Overview
Some monkeypatches upon ActiveRecord::Base
Constant Summary collapse
- @@cache_local =
{}
- @@use_local_cache =
false- @@ttl =
60 * 15
Class Method Summary collapse
-
.cache_delete(klass, id) ⇒ Object
Invalidate the cache entry for an record.
-
.cache_key_local(klass, id) ⇒ Object
The local cache key for this record.
-
.cache_key_memcache(klass, id) ⇒ Object
The memcache key for this record.
-
.cache_reset ⇒ Object
Invalidate the local process cache.
-
.cached_finders(*args) ⇒ Object
Tell this class to use memcache_memoize to cache certain finders.
- .cached_supermodel_decrement_counter ⇒ Object
- .cached_supermodel_delete ⇒ Object
- .cached_supermodel_find ⇒ Object
- .cached_supermodel_find_by_sql ⇒ Object
- .cached_supermodel_increment_counter ⇒ Object
- .decrement_counter(counter_name, id) ⇒ Object
- .delete(id) ⇒ Object
- .find(*args) ⇒ Object
- .find_by_sql(*args) ⇒ Object
- .increment_counter(counter_name, id) ⇒ Object
- .ok_primary_key(key) ⇒ Object
- .ttl ⇒ Object
- .ttl=(t) ⇒ Object
- .use_local_cache=(u) ⇒ Object
- .use_local_cache? ⇒ Boolean
Instance Method Summary collapse
-
#cache_delete ⇒ Object
Remove this record from the cache.
- #cache_key_local ⇒ Object
- #cache_key_memcache ⇒ Object
-
#cache_local ⇒ Object
The local object cache.
-
#cache_store ⇒ Object
Store this record in the cache without associations.
-
#cached_supermodel_destroy ⇒ Object
Delete the entry from the cache now that it isn’t in the DB.
-
#cached_supermodel_reload ⇒ Object
Invalidate the cache for this record before reloading from the DB.
-
#cached_supermodel_update ⇒ Object
Store a new copy of ourselves into the cache.
- #destroy ⇒ Object
- #reload ⇒ Object
-
#remove_associations ⇒ Object
Return a copy of this instance with all association-created attributes removed.
-
#remove_associations! ⇒ Object
Removes all association-created attributes from this instance.
- #update ⇒ Object
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.
207 208 209 210 211 |
# File 'lib/cs_active_record.rb', line 207 def self.cache_delete(klass, id) self.cache_local.delete self.cache_key_local(klass, id) if self.use_local_cache? CACHE.delete self.cache_key_memcache(klass, id) logger.debug("deleted #{self.cache_key_memcache(klass, id)}") end |
.cache_key_local(klass, id) ⇒ Object
The local cache key for this record.
364 365 366 |
# File 'lib/cs_active_record.rb', line 364 def self.cache_key_local(klass, id) return "#{klass}:#{id}" end |
.cache_key_memcache(klass, id) ⇒ Object
The memcache key for this record.
375 376 377 |
# File 'lib/cs_active_record.rb', line 375 def self.cache_key_memcache(klass, id) return "active_record:#{self.cache_key_local(klass, id)}" end |
.cache_reset ⇒ Object
Invalidate the local process cache. This should be called from a before filter at the beginning of each request.
217 218 219 |
# File 'lib/cs_active_record.rb', line 217 def self.cache_reset self.cache_local.clear if self.use_local_cache? end |
.cached_finders(*args) ⇒ Object
Tell this class to use memcache_memoize to cache certain finders.
The results of the finders will be automatically cached and reused (unless you give extra conditions, which will turn off the caching completely!).
Invalidation will occur on save and destroy.
Usage:
class MyModel < ActiveRecord::Base
cached_finders :find_by_id_and_name_and_something_else
end
43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 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 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 |
# File 'lib/cs_active_record.rb', line 43 def self.cached_finders(*args) args.each do |finder| if (match = finder.to_s.match(/^find_(all_)?by_(.*)$/)) attributes = match[2].to_s.split(/_and_/) define_method("invalidate_cached_#{finder}") do unless self.new_record? begin self.class.find(self.id).send("non_recursive_invalidate_cached_#{finder}") rescue Exception => e # This can fail just because our saving broke a transaction, in that case # we wont exist, even if we happen to have an id (and !new_record?) end end self.send("non_recursive_invalidate_cached_#{finder}") end define_method("non_recursive_invalidate_cached_#{finder}") do attribute_values = attributes.collect do |attribute| self[attribute.to_sym] end expire_cached_namespace("CachedSupermodel:" + self.class.name + ":#{finder}:" + attribute_values.inspect) end else raise "Unknown finder type #{finder}" end class_eval " def self.#{finder}_get_ids(*args) match = '#{finder}'.match(/^find_(all_)?by_(.*)$/) # flag for find / find_all find_all = '#{finder}'.match(/^find_all_by/) ? true : false attributes = match[2].to_s.split(/_and_/) normal_arguments = args[0...attributes.size] key_type = columns_hash[self.primary_key].type rest = args[attributes.size..-1] hash_args = rest.first || {} cache_value(['CachedSupermodel:' + self.name + ':#{finder}:' + normal_arguments.inspect, rest.inspect]) do params = [] query = 'SELECT ' + self.primary_key.to_s + ' from ' + self.table_name where = [] attributes.each_with_index do |a, i| if args[i].nil? where << a + ' IS NULL' else where << a + ' = ?' params << args[i] end end query << (' WHERE ' + where.join(' AND ')) if hash_args.include?(:order) query << ' ORDER BY ' << hash_args[:order] end if find_all if hash_args.include?(:limit) query << ' LIMIT ?' params << hash_args[:limit] end else query << ' LIMIT 1' end if hash_args.include?(:offset) query << ' OFFSET ?' params << hash_args[:offset] end rval = self.connection.select_all(self.sanitizeSQL([query, *params])).collect{ |row| if key_type == :integer row[self.primary_key.to_s].to_i else row[self.primary_key.to_s] end }.compact if find_all rval else if rval.blank? nil else rval.first end end end end def self.#{finder}(*args) begin self.#{finder}_orig(*args) rescue ActiveRecord::RecordNotFound nil end end def self.#{finder}!(*args) self.#{finder}_orig(*args) end def self.#{finder}_orig(*args) match = '#{finder}'.match(/^find_(all_)?by_(.*)$/) attributes = match[2].to_s.split(/_and_/) normal_arguments = args[0...attributes.size] rest = args[attributes.size..-1] extra_conditions = false rest.each do |arg| if Hash === arg extra_conditions = extra_conditions || arg.include?(:conditions) end end if extra_conditions method_missing(:#{finder}, *args) else rval = self.#{finder}_get_ids(*args) if Array === rval rval.collect do |oid| self.find(oid) rescue nil end.compact else if rval.nil? raise ActiveRecord::RecordNotFound else self.find(rval) end end end end before_save :invalidate_cached_#{finder} before_destroy :invalidate_cached_#{finder} " end end |
.cached_supermodel_decrement_counter ⇒ Object
422 |
# File 'lib/cs_active_record.rb', line 422 alias_method :cached_supermodel_decrement_counter, :decrement_counter |
.cached_supermodel_delete ⇒ Object
438 |
# File 'lib/cs_active_record.rb', line 438 alias_method :cached_supermodel_delete, :delete |
.cached_supermodel_find ⇒ Object
237 |
# File 'lib/cs_active_record.rb', line 237 alias_method :cached_supermodel_find, :find |
.cached_supermodel_find_by_sql ⇒ Object
304 |
# File 'lib/cs_active_record.rb', line 304 alias_method :cached_supermodel_find_by_sql, :find_by_sql |
.cached_supermodel_increment_counter ⇒ Object
406 |
# File 'lib/cs_active_record.rb', line 406 alias_method :cached_supermodel_increment_counter, :increment_counter |
.decrement_counter(counter_name, id) ⇒ Object
425 426 427 428 429 430 431 |
# File 'lib/cs_active_record.rb', line 425 def self.decrement_counter(counter_name, id) begin return cached_supermodel_decrement_counter(counter_name, id) ensure cache_delete(self, id) end end |
.delete(id) ⇒ Object
441 442 443 444 445 446 447 448 449 450 451 452 453 |
# File 'lib/cs_active_record.rb', line 441 def self.delete(id) begin return cached_supermodel_delete(id) ensure if id.respond_to?(:each) id.each do |i| cache_delete(self.class, i) end else cache_delete(self.class, id) end end end |
.find(*args) ⇒ Object
240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 |
# File 'lib/cs_active_record.rb', line 240 def self.find(*args) args.reject! do |arg| arg.is_a?(Hash) && arg.values.compact.empty? end 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 !ok_primary_key(args.first) then records = cached_supermodel_find(*args) # Rails requires two levels of indirection to look up a record return records if args.first == :all and @skip_find_hack case records when Array then records.each { |r| r.cache_store } when ActiveRecord then records.cache_store # Model.find 1 gets cached here end return records end # Try to find the record in the local cache. id = args.first if self.use_local_cache? then record = self.cache_local[self.cache_key_local(name, id)] return record unless record.nil? end # Try to find the record in memcache and add it to the local cache record = CACHE.get self.cache_key_memcache(name, id) unless record == :MemCache_no_such_entry then logger.debug("found #{self.cache_key_memcache(name, id)}") record = nil if record == :MemCache_nil if self.use_local_cache? then self.cache_local[self.cache_key_local(name, id)] = record end return record end # Fetch the record from the DB. Inside the multiple levels of indirection # of find it will get cached. (no it wont, so i added a cache_store below //martin) # # We don't want the subsequent find_by_sql to loop back here, so guard # the call. # # NOTE This guard is not thread safe, beware use of cached ActiveRecord where # ActiveRecord's thread safety is disabled. begin @skip_find_hack = true record = cached_supermodel_find(args).first record.cache_store ensure @skip_find_hack = false end return record end |
.find_by_sql(*args) ⇒ Object
307 308 309 310 311 312 313 314 315 |
# File 'lib/cs_active_record.rb', line 307 def self.find_by_sql(*args) unless @skip_find_hack if args.first =~ /SELECT \* FROM #{table_name} WHERE \(#{table_name}\.#{primary_key} = '?(\d+)'?\) LIMIT 1/ then return [find($1.to_i)] end end return cached_supermodel_find_by_sql(*args) end |
.increment_counter(counter_name, id) ⇒ Object
409 410 411 412 413 414 415 |
# File 'lib/cs_active_record.rb', line 409 def self.increment_counter(counter_name, id) begin return cached_supermodel_increment_counter(counter_name, id) ensure cache_delete(self, id) end end |
.ok_primary_key(key) ⇒ Object
221 222 223 224 225 226 227 228 229 230 |
# File 'lib/cs_active_record.rb', line 221 def self.ok_primary_key(key) case self.columns_hash[self.primary_key].type when :integer key.is_a?(Fixnum) when :string key.is_a?(String) else raise "I dont know about this column type: #{self.columns_hash[self.primary_key_column].type}" end end |
.ttl ⇒ Object
180 181 182 |
# File 'lib/cs_active_record.rb', line 180 def self.ttl @@ttl end |
.ttl=(t) ⇒ Object
184 185 186 |
# File 'lib/cs_active_record.rb', line 184 def self.ttl=(t) @@ttl = t end |
.use_local_cache=(u) ⇒ Object
192 193 194 |
# File 'lib/cs_active_record.rb', line 192 def self.use_local_cache=(u) @@use_local_cache = u end |
.use_local_cache? ⇒ Boolean
188 189 190 |
# File 'lib/cs_active_record.rb', line 188 def self.use_local_cache? @@use_local_cache end |
Instance Method Details
#cache_delete ⇒ Object
Remove this record from the cache.
355 356 357 358 359 |
# File 'lib/cs_active_record.rb', line 355 def cache_delete cache_local.delete cache_key_local if self.class.use_local_cache? CACHE.delete cache_key_memcache logger.debug("deleted #{cache_key_memcache}") end |
#cache_key_local ⇒ Object
368 369 370 |
# File 'lib/cs_active_record.rb', line 368 def cache_key_local self.class.cache_key_local(self.class, self.id) end |
#cache_key_memcache ⇒ Object
379 380 381 |
# File 'lib/cs_active_record.rb', line 379 def cache_key_memcache self.class.cache_key_memcache(self.class, self.id) end |
#cache_local ⇒ Object
The local object cache.
386 387 388 |
# File 'lib/cs_active_record.rb', line 386 def cache_local return self.class.cache_local end |
#cache_store ⇒ Object
Store this record in the cache without associations. Storing associations leads to wasted cache space and hard-to-debug problems.
393 394 395 396 397 398 399 |
# File 'lib/cs_active_record.rb', line 393 def cache_store if self.class.use_local_cache? then cache_local[cache_key_local] = remove_associations end CACHE.set cache_key_memcache, remove_associations, self.class.ttl logger.debug("stored #{cache_key_memcache}") end |
#cached_supermodel_destroy ⇒ Object
Delete the entry from the cache now that it isn’t in the DB.
320 |
# File 'lib/cs_active_record.rb', line 320 alias_method :cached_supermodel_destroy, :destroy |
#cached_supermodel_reload ⇒ Object
Invalidate the cache for this record before reloading from the DB.
331 |
# File 'lib/cs_active_record.rb', line 331 alias_method :cached_supermodel_reload, :reload |
#cached_supermodel_update ⇒ Object
Store a new copy of ourselves into the cache.
343 |
# File 'lib/cs_active_record.rb', line 343 alias_method :cached_supermodel_update, :update |
#destroy ⇒ Object
322 323 324 325 326 |
# File 'lib/cs_active_record.rb', line 322 def destroy return cached_supermodel_destroy ensure cache_delete end |
#reload ⇒ Object
333 334 335 336 337 338 |
# File 'lib/cs_active_record.rb', line 333 def reload cache_delete return cached_supermodel_reload ensure cache_store end |
#remove_associations ⇒ Object
Return a copy of this instance with all association-created attributes removed.
24 25 26 27 28 |
# File 'lib/cs_active_record.rb', line 24 def remove_associations obj = dup obj.remove_associations! obj end |
#remove_associations! ⇒ Object
Removes all association-created attributes from this instance.
11 12 13 14 15 16 17 18 19 20 |
# File 'lib/cs_active_record.rb', line 11 def remove_associations! instance_variable_set(:@attributes, attributes_before_type_cast) instance_variables.collect do |var| var[1..-1] end.each do |var| unless self.class.columns_hash.merge({"new_record" => true, "attributes" => true}).include?(var) instance_variable_set("@#{var}", nil) end end end |
#update ⇒ Object
345 346 347 348 349 |
# File 'lib/cs_active_record.rb', line 345 def update return cached_supermodel_update ensure cache_store end |