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
-
.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.
211 212 213 214 215 |
# File 'lib/cs_active_record.rb', line 211 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
372 373 374 375 |
# File 'lib/cs_active_record.rb', line 372 def self.cache_key_local(klass, id) klass = Module.const_get(klass) unless Class === klass return "#{klass.base_class.name}:#{id}" end |
.cache_key_memcache(klass, id) ⇒ Object
The memcache key for this record.
384 385 386 |
# File 'lib/cs_active_record.rb', line 384 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.
221 222 223 |
# File 'lib/cs_active_record.rb', line 221 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 175 176 177 178 |
# 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 unless descends_from_active_record? where << type_condition 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.send(:sanitize_sql, [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.base_class.find(oid) rescue nil end.compact else if rval.nil? raise ActiveRecord::RecordNotFound else self.base_class.find(rval) end end end end before_save :invalidate_cached_#{finder} before_destroy :invalidate_cached_#{finder} " end end |
.cached_supermodel_decrement_counter ⇒ Object
431 |
# File 'lib/cs_active_record.rb', line 431 alias_method :cached_supermodel_decrement_counter, :decrement_counter |
.cached_supermodel_delete ⇒ Object
447 |
# File 'lib/cs_active_record.rb', line 447 alias_method :cached_supermodel_delete, :delete |
.cached_supermodel_find ⇒ Object
241 |
# File 'lib/cs_active_record.rb', line 241 alias_method :cached_supermodel_find, :find |
.cached_supermodel_find_by_sql ⇒ Object
308 |
# File 'lib/cs_active_record.rb', line 308 alias_method :cached_supermodel_find_by_sql, :find_by_sql |
.cached_supermodel_increment_counter ⇒ Object
415 |
# File 'lib/cs_active_record.rb', line 415 alias_method :cached_supermodel_increment_counter, :increment_counter |
.decrement_counter(counter_name, id) ⇒ Object
434 435 436 437 438 439 440 |
# File 'lib/cs_active_record.rb', line 434 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
450 451 452 453 454 455 456 457 458 459 460 461 462 |
# File 'lib/cs_active_record.rb', line 450 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
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 297 298 299 300 |
# File 'lib/cs_active_record.rb', line 244 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
311 312 313 314 315 316 317 318 319 |
# File 'lib/cs_active_record.rb', line 311 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
418 419 420 421 422 423 424 |
# File 'lib/cs_active_record.rb', line 418 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
225 226 227 228 229 230 231 232 233 234 |
# File 'lib/cs_active_record.rb', line 225 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
184 185 186 |
# File 'lib/cs_active_record.rb', line 184 def self.ttl @@ttl end |
.ttl=(t) ⇒ Object
188 189 190 |
# File 'lib/cs_active_record.rb', line 188 def self.ttl=(t) @@ttl = t end |
.use_local_cache=(u) ⇒ Object
196 197 198 |
# File 'lib/cs_active_record.rb', line 196 def self.use_local_cache=(u) @@use_local_cache = u end |
.use_local_cache? ⇒ Boolean
192 193 194 |
# File 'lib/cs_active_record.rb', line 192 def self.use_local_cache? @@use_local_cache end |
Instance Method Details
#cache_delete ⇒ Object
Remove this record from the cache.
359 360 361 362 363 |
# File 'lib/cs_active_record.rb', line 359 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
377 378 379 |
# File 'lib/cs_active_record.rb', line 377 def cache_key_local self.class.cache_key_local(self.class, self.id) end |
#cache_key_memcache ⇒ Object
388 389 390 |
# File 'lib/cs_active_record.rb', line 388 def cache_key_memcache self.class.cache_key_memcache(self.class, self.id) end |
#cache_local ⇒ Object
The local object cache.
395 396 397 |
# File 'lib/cs_active_record.rb', line 395 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.
402 403 404 405 406 407 408 |
# File 'lib/cs_active_record.rb', line 402 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.
324 |
# File 'lib/cs_active_record.rb', line 324 alias_method :cached_supermodel_destroy, :destroy |
#cached_supermodel_reload ⇒ Object
Invalidate the cache for this record before reloading from the DB.
335 |
# File 'lib/cs_active_record.rb', line 335 alias_method :cached_supermodel_reload, :reload |
#cached_supermodel_update ⇒ Object
Store a new copy of ourselves into the cache.
347 |
# File 'lib/cs_active_record.rb', line 347 alias_method :cached_supermodel_update, :update |
#destroy ⇒ Object
326 327 328 329 330 |
# File 'lib/cs_active_record.rb', line 326 def destroy return cached_supermodel_destroy ensure cache_delete end |
#reload ⇒ Object
337 338 339 340 341 342 |
# File 'lib/cs_active_record.rb', line 337 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
349 350 351 352 353 |
# File 'lib/cs_active_record.rb', line 349 def update return cached_supermodel_update ensure cache_store end |