Created
June 12, 2012 00:14
-
-
Save xaviershay/2913572 to your computer and use it in GitHub Desktop.
DatabaseCacheStore
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Implementation of Rails.cache backed by our database. We don't have high | |
# performance or space requirements, so using existing infrastructure for our | |
# caching needs is desirable. | |
class DatabaseCacheStore < ActiveSupport::Cache::Store | |
class CacheEntry < ActiveRecord::Base | |
end | |
def self.migrate_up(m) | |
m.create_table :cache_entries do |t| | |
t.string :key, :null => false | |
t.text :value, :null => false | |
t.datetime :expires_at, :null => false | |
end | |
# Because JDBC won't have it any other way | |
m.execute "ALTER TABLE cache_entries MODIFY COLUMN value mediumtext" | |
m.add_index :cache_entries, :key, :unique => true | |
m.add_index :cache_entries, :expires_at | |
end | |
def self.migrate_down(m) | |
m.drop_table :cache_entries if m.table_exists?(:cache_entries) | |
end | |
def self.purge | |
CacheEntry.delete_all | |
end | |
def self.trim | |
CacheEntry.where(['expires_at < ?', Time.now]).delete_all | |
end | |
def self.size | |
CacheEntry.count | |
end | |
attr_reader :options | |
def initialize(options={}) | |
@options = options | |
end | |
def expires_in | |
@expires_in ||= options[:expires_in] || 1.hour | |
end | |
protected | |
def read_entry(key, options) | |
existing = CacheEntry.find_by_key(key) | |
if existing && Time.now < existing.expires_at | |
value = Marshal.load(Base64.decode64(existing.value)) | |
ActiveSupport::Cache::Entry.new(value) | |
end | |
end | |
def write_entry(key, entry, options) | |
expires = Time.now + options.fetch(:expires_in, expires_in) | |
value = entry.value | |
attributes = { | |
:key => key, | |
:value => Base64.encode64(Marshal.dump(value)), | |
:expires_at => expires | |
} | |
begin | |
existing = CacheEntry.find_by_key(key) | |
if existing | |
existing.update_attributes(attributes) | |
else | |
CacheEntry.create!(attributes) | |
end | |
rescue ActiveRecord::RecordNotUnique | |
retry | |
end | |
end | |
def delete_entry(key, options) | |
CacheEntry.find_by_key(key).try(:destroy) | |
end | |
end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
require 'integration_helper' | |
describe DatabaseCacheStore do | |
let(:store) { DatabaseCacheStore.new } | |
it 'round-trips strings to the database' do | |
store.write('a', 'value') | |
store.read('a').should == 'value' | |
end | |
it 'round-trips hashes to the database' do | |
store.write('a', {}) | |
store.read('a').should == {} | |
end | |
it 'round-trips arrays to the database' do | |
store.write('a', []) | |
store.read('a').should == [] | |
end | |
it 'round-trips ints to the database' do | |
store.write('a', 1) | |
store.read('a').should == 1 | |
end | |
it 'supports expiry' do | |
store.write('a', 'value', :expires_in => 0.seconds) | |
store.read('a').should be_nil | |
store.write('a', 'value', :expires_in => 1.minute) | |
store.read('a').should_not be_nil | |
end | |
it 'supports update' do | |
store.write('a', 'value') | |
store.write('a', 'new value') | |
store.read('a').should == 'new value' | |
end | |
it 'can handle missing values' do | |
store.read('a').should be_nil | |
store.delete('a') # Should be a noop | |
store.read('a').should be_nil | |
end | |
it 'handles insert race condition' do | |
# I don't know how to test this properly :( | |
end | |
it 'can delete values' do | |
store.write('a', 'value') | |
store.delete('a') | |
store.read('a').should be_nil | |
end | |
it 'purges values' do | |
store.write('a', 'value') | |
store.class.purge | |
store.read('a').should be_nil | |
end | |
it 'trims values' do | |
old_size = store.class.size | |
store.write('a', 'value', :expires_in => -1.seconds) | |
store.write('b', 'value', :expires_in => 1.minute) | |
store.class.trim | |
store.class.size.should == old_size + 1 | |
store.read('a').should be_nil | |
store.read('b').should == 'value' | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment