Skip to content

Commit

Permalink
importing initial files
Browse files Browse the repository at this point in the history
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
Show file tree
Hide file tree
Showing 4 changed files with 222 additions and 0 deletions.
147 changes: 147 additions & 0 deletions lib/timed_cache.rb
@@ -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
67 changes: 67 additions & 0 deletions specs/timed_cache_spec.rb
@@ -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
4 changes: 4 additions & 0 deletions svn-commit.2.tmp
@@ -0,0 +1,4 @@
importing initial files
--This line, and those below, will be ignored--

A .
4 changes: 4 additions & 0 deletions svn-commit.tmp
@@ -0,0 +1,4 @@
importing initial files
--This line, and those below, will be ignored--

A .

0 comments on commit 9f37c8e

Please sign in to comment.