require 'test/unit' $TESTING = :cached_model RAILS_ENV = 'production' module ActiveRecord; end class ActiveRecord::Base @count = 1000 attr_accessor :id, :attributes def self.next_id @count += 1 return @count end def self.table_name name.downcase end def self.primary_key 'id' end def self.find(*args) args.flatten! return [new] if args.length == 1 and Fixnum === args.first return new if args.length == 2 return [new, new, new] if args.length == 3 end def self.find_by_sql(query) return [new] end def initialize @id = ActiveRecord::Base.next_id @attributes = { :data => 'data' } end def ==(other) self.class == other.class and id == other.id and attributes_before_type_cast == other.attributes_before_type_cast end def attributes_before_type_cast { :data => @attributes[:data] } end def destroy end def reload @attributes[:data].succ! @attributes[:extra] = nil end def update end end require 'cached_model' class Concrete < CachedModel; end class STILameness < Concrete; end module Cache @cache = {} @ttl = {} class << self; attr_accessor :cache, :ttl; end def self.delete(key) @cache.delete key @ttl.delete key return nil end def self.get(key) @cache[key] end def self.put(key, value, ttl) value = Marshal.load Marshal.dump(value) @cache[key] = value @ttl[key] = ttl end end class << CachedModel attr_writer :cache_local end class TestCachedModel < Test::Unit::TestCase def setup @model = Concrete.new Cache.cache = {} Cache.ttl = {} CachedModel.cache_local = {} end def test_class_cache_delete util_set CachedModel.cache_delete @model.class, @model.id assert_equal true, CachedModel.cache_local.empty? assert_equal true, Cache.cache.empty? end def test_class_cache_reset util_set CachedModel.cache_reset assert_equal true, CachedModel.cache_local.empty? assert_equal false, Cache.cache.empty? end def test_class_descends_from_active_record? assert_equal true, Concrete.descends_from_active_record? assert_equal false, STILameness.descends_from_active_record? end def test_class_find_complex record = Concrete.find(1, :order => 'lock_version') assert_equal @model.id + 1, record.id assert_equal record, CachedModel.cache_local[record.cache_key_local] assert_equal record, Cache.cache[record.cache_key_memcache] end def test_class_find_in_local_cache util_set record = Concrete.find(1, :order => 'lock_version') assert_equal @model.id + 1, record.id assert_equal record, CachedModel.cache_local[record.cache_key_local] assert_equal record, Cache.cache[record.cache_key_memcache] end def test_class_find_in_memcache util_set CachedModel.cache_reset record = Concrete.find @model.id assert_equal @model, record assert_equal record, CachedModel.cache_local[record.cache_key_local] end def test_class_find_multiple ids = [@model.id + 1, @model.id + 2, @model.id + 3] records = Concrete.find(*ids) assert_equal ids, records.map { |r| r.id } assert_equal ids.map { |i| "#{@model.class}:#{i}" }, CachedModel.cache_local.keys assert_equal ids.map { |i| "#{CachedModel::KEY}:#{@model.class}:#{i}" }, Cache.cache.keys end def test_class_find_not_cached record = Concrete.find @model.id + 1 assert_equal @model.id + 1, record.id assert_equal record, CachedModel.cache_local[record.cache_key_local] assert_equal record, Cache.cache[record.cache_key_memcache] end def test_class_find_by_sql q = "SELECT * FROM concrete WHERE (concrete.id = #{@model.id + 1}) LIMIT 1" record = Concrete.find_by_sql(q).first assert_equal @model.id + 1, record.id assert_equal record, CachedModel.cache_local[record.cache_key_local] assert_equal record, Cache.cache[record.cache_key_memcache] end def test_class_find_by_sql_skip_hack Concrete.instance_variable_set :@skip_find_hack, true q = "SELECT * FROM concrete WHERE (concrete.id = #{@model.id + 1}) LIMIT 1" record = Concrete.find_by_sql(q).first assert_equal @model.id + 1, record.id assert_equal true, CachedModel.cache_local.empty? assert_equal true, Cache.cache.empty? ensure Concrete.instance_variable_set :@skip_find_hack, false end def test_class_name_of_active_record_descendant assert_equal "Concrete", CachedModel.class_name_of_active_record_descendant(Concrete) assert_equal "Concrete", CachedModel.class_name_of_active_record_descendant(STILameness) end def test_cache_delete util_set @model.cache_delete assert_equal true, CachedModel.cache_local.empty? assert_equal true, Cache.cache.empty? end def test_cache_key_local assert_equal "#{@model.class}:#{@model.id}", @model.cache_key_local end def test_cache_key_memcache assert_equal "#{CachedModel::KEY}:#{@model.class}:#{@model.id}", @model.cache_key_memcache end def test_cache_local assert_same CachedModel.cache_local, @model.cache_local end def test_cache_store @model.cache_store assert_equal false, CachedModel.cache_local.empty? assert_equal false, Cache.cache.empty? assert_equal @model, CachedModel.cache_local[@model.cache_key_local] assert_equal @model, Cache.cache[@model.cache_key_memcache] assert_equal CachedModel::TTL, Cache.ttl[@model.cache_key_memcache] end def test_cache_store_with_attributes @model.attributes[:extra] = 'extra' @model.cache_store assert_equal false, CachedModel.cache_local.empty? assert_equal false, Cache.cache.empty? local_model = CachedModel.cache_local[@model.cache_key_local] mem_model = Cache.cache[@model.cache_key_memcache] assert_equal @model, local_model assert_equal @model, mem_model assert_equal CachedModel::TTL, Cache.ttl[@model.cache_key_memcache] expected = {:data => 'data'} assert_equal expected, local_model.attributes assert_equal expected, mem_model.attributes end def test_destroy util_set @model.destroy assert_equal true, CachedModel.cache_local.empty? assert_equal true, Cache.cache.empty? end def test_reload util_set @model.reload assert_equal false, CachedModel.cache_local.empty? assert_equal false, Cache.cache.empty? assert_equal 'datb', CachedModel.cache_local[@model.cache_key_local].attributes[:data] assert_equal 'datb', Cache.cache[@model.cache_key_memcache].attributes[:data] end def test_update util_set @model.attributes[:data] = 'atad' @model.update assert_equal false, CachedModel.cache_local.empty? assert_equal false, Cache.cache.empty? assert_equal 'atad', CachedModel.cache_local[@model.cache_key_local].attributes[:data] assert_equal 'atad', Cache.cache[@model.cache_key_memcache].attributes[:data] end def util_set(klass = @model.class, id = @model.id, data = @model) key = "#{klass}:#{id}" CachedModel.cache_local[key] = data Cache.cache["#{CachedModel::KEY}:#{key}"] = data end end