Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
git-svn-id: svn+ssh://rubyforge.org/var/svn/timedcache/trunk@2 86bfafc1-3100-4244-9143-2200d8b62c5a
- Loading branch information
nickdainty
committed
Nov 17, 2006
1 parent
1fec5e9
commit 9f37c8e
Showing
4 changed files
with
222 additions
and
0 deletions.
There are no files selected for viewing
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,147 @@ | ||
require "pstore" | ||
|
||
# TimedCache implements a cache in which you can place objects | ||
# and specify a timeout value. | ||
# | ||
# If you attempt to retrieve the object within the specified timeout | ||
# period, the object will be returned. If the timeout period has elapsed, | ||
# the TimedCache will return nil. | ||
# | ||
# e.g.: | ||
# cache = TimedCache.new | ||
# cache.put :my_object_key, "Expensive data", 10 # => "Expensive data" | ||
# cache.get :my_object_key # => "Expensive data" | ||
# | ||
# ... 10 seconds later: | ||
# cache.get :my_object_key # => nil | ||
# | ||
# = Default timeout | ||
# | ||
# When creating a new TimedCache, a default timeout value can be set. This value | ||
# will be used for each object added to the cache, unless a different timeout value | ||
# is specifically set for that object. | ||
# | ||
# e.g.: | ||
# | ||
# cache = TimedCache.new(:default_timeout => 120) | ||
# cache.default_timeout # => 120 | ||
# | ||
# = File-based cache | ||
# | ||
# By default, TimedCache will use an in-memory store. A file-based store (using the | ||
# PStore library) can also be used. | ||
# | ||
# e.g.: | ||
# | ||
# TimedCache.new(:type => :file, :filename => "my_cache.db") | ||
# | ||
class TimedCache | ||
attr_reader :default_timeout | ||
|
||
def initialize(opts = {}) | ||
opts[:type] ||= :memory | ||
@default_timeout = opts[:default_timeout] || 60 | ||
@store = new_store(opts) | ||
end | ||
|
||
def put(key, value, timeout = @default_timeout) | ||
@store.put(key, value, timeout) | ||
end | ||
|
||
def get(key) | ||
@store.get(key) | ||
end | ||
|
||
def []=(key, value) | ||
put(key, value) | ||
end | ||
|
||
def [](key) | ||
get(key) | ||
end | ||
|
||
protected | ||
|
||
def new_store(options) | ||
self.class.const_get(options[:type].to_s.capitalize + "Store").new(options) | ||
end | ||
|
||
class Store #:nodoc: | ||
def initialize(options) | ||
@options = options | ||
end | ||
end | ||
|
||
class MemoryStore < Store #:nodoc: | ||
def initialize(options) | ||
super | ||
@cache = Hash.new | ||
end | ||
|
||
def put(key, value, timeout) | ||
@cache[key.to_s.intern] = ObjectContainer.new(value, timeout) | ||
# Return just the given value, so that references to the | ||
# ObjectStore instance can't be held outside this TimedCache: | ||
value | ||
end | ||
|
||
def get(key) | ||
if object_store = @cache[key.to_s.intern] | ||
if object_store.expired? | ||
# Free up memory: | ||
@cache[key.to_s.intern] = nil | ||
else | ||
object_store.object | ||
end | ||
end | ||
end | ||
end | ||
|
||
class FileStore < Store #:nodoc: | ||
def initialize(*args) | ||
super(*args) | ||
filename = @options[:filename] | ||
unless filename | ||
raise ArgumentError, ":filename option must be specified for :file type store." | ||
end | ||
@cache = PStore.new(filename) | ||
end | ||
|
||
def put(key, value, timeout = nil) | ||
@cache.transaction do | ||
@cache[key.to_s.intern] = ObjectContainer.new(value, timeout) | ||
end | ||
|
||
# Return just the given value, so that references to the | ||
# ObjectStore instance can't be held outside this TimedCache: | ||
value | ||
end | ||
|
||
def get(key) | ||
@cache.transaction do | ||
if object_store = @cache[key.to_s.intern] | ||
if object_store.expired? | ||
# Free up memory: | ||
@cache[key.to_s.intern] = nil | ||
else | ||
object_store.object | ||
end | ||
end | ||
end | ||
end | ||
end | ||
|
||
class ObjectContainer #:nodoc: | ||
attr_reader :object | ||
|
||
def initialize(object, timeout) | ||
@created_at = Time.now.utc | ||
@timeout = timeout | ||
@object = object | ||
end | ||
|
||
def expired? | ||
(Time.now.utc - @timeout) > @created_at | ||
end | ||
end | ||
end |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
require File.join(File.dirname(__FILE__), "../timed_cache") | ||
|
||
$filename = File.join(File.dirname(__FILE__), "specs.db") | ||
|
||
context "Adding and retrieving objects from the cache" do | ||
setup do | ||
@memory_cache = TimedCache.new | ||
@file_cache = TimedCache.new(:type => :file, :filename => $filename) | ||
@caches = [@memory_cache, @file_cache] | ||
end | ||
|
||
teardown do | ||
File.delete($filename) | ||
end | ||
|
||
specify "Can add an object to the cache, specifying a timeout value" do | ||
@caches.each do |cache| | ||
cache.put(:myobject, "This needs caching", 10).should_equal "This needs caching" | ||
end | ||
end | ||
|
||
specify "Cache should hold seperate values for each key" do | ||
@caches.each do |cache| | ||
cache.put(:myobject, "This needs caching", 10).should_equal "This needs caching" | ||
cache.put(:my_other_object, "...and this too", 10).should_equal "...and this too" | ||
cache.get(:myobject).should_equal "This needs caching" | ||
cache.get(:my_other_object).should_equal "...and this too" | ||
end | ||
end | ||
|
||
specify "After the specified timeout value has elapsed, nil should be returned" do | ||
@caches.each do |cache| | ||
cache.put(:myobject, "This needs caching", 0).should_equal "This needs caching" | ||
cache.get(:myobject).should_equal nil | ||
end | ||
end | ||
|
||
specify "If no object matching the given key is found, nil should be returned" do | ||
@caches.each do |cache| | ||
cache.get(:my_nonexistant_object).should_equal nil | ||
end | ||
end | ||
|
||
specify "Should be able to use an array as a cache key" do | ||
@caches.each do |cache| | ||
cache.put([123,234], "Array").should_equal "Array" | ||
cache.get([123,234]).should_equal "Array" | ||
end | ||
end | ||
end | ||
|
||
context "Specifying a default timeout" do | ||
specify "Should be able to specify a default timeout when creating a TimedCache" do | ||
cache = TimedCache.new(:default_timeout => 20) | ||
cache.should_be_kind_of TimedCache | ||
cache.default_timeout.should_equal 20 | ||
end | ||
|
||
specify "If no default timeout is set, 60 seconds should be used" do | ||
cache = TimedCache.new | ||
cache.should_be_kind_of TimedCache | ||
cache.default_timeout.should_equal 60 | ||
end | ||
|
||
specify "Timeout specified when putting a new object into the cache should override default timeout" do | ||
end | ||
end |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
importing initial files | ||
--This line, and those below, will be ignored-- | ||
|
||
A . |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
importing initial files | ||
--This line, and those below, will be ignored-- | ||
|
||
A . |