But rather than either an array or hash, what if you could write:
FOO=Cache.new
That is, FOO would behave mostly like a hash, but unused entries would
age out after a while. Now if only someone would invent an appropriate
Cache class.
Undocumented and without a test framework, I offer you a class I wrote
a while ago that's still live and in use a couple of years later. It's
not exactly what you're asking for, but close. (It returns string-
based keys for accessing the objects because my usage has it running
in a separate process from the main app.) I reasonably hate the
decision I made to return values versus arrays based on number of
arguments, and it's not as DRY as it could be, but it's working
# Example usage
# class SituationServer < Phrogz::Librarian
# ...
# end
#
# CLEANING_SCHEDULE = 60*60 # Clean up once an hour
# MAX_STALENESS = 60*60 # Remove items not used in the last
hour
#
# @server = SituationServer.new
# Thread.new{
# loop {
# sleep CLEANING_SCHEDULE
# removed = @server.remove_stale( MAX_STALENESS )
# puts "Just removed #{removed} stale item#{:s unless
removed==1}..."
# }
# }
module Phrogz; end
class Phrogz::Librarian
def initialize( key_size=4 )
@key_size = key_size
@max_keys = 16 ** key_size
@serializer = "%0#{key_size}x"
@library = {}
@stored = {}
@access = {}
end
def store( *objects )
keys = objects.map{ |object|
key,obj = @library.find{ |k,o| o==object }
unless key
# FIXME: bail if @library.size >= MAX_KEYS, or else this will
infinite loop
# TODO: come up with a better strategy for finding a free key
begin
key = @serializer % rand( @max_keys )
end while @library.has_key?( key )
@library[ key ] = object
@stored[ key ] = Time.now
end
key
}
keys.length == 1 ? keys.first : keys
end
alias_method :<<, :store
def fetch( *keys )
now = Time.now # So that multiple accesses are exactly
synchronized
objects = keys.map{ |key|
if @library.has_key?( key )
@access[ key ] = now
@library[ key ]
end
}
objects.length == 1 ? objects.first : objects
end
def size
@library.size
end
def storage_time( *keys )
results = @stored.values_at( *keys )
results.length == 1 ? results.first : results
end
def age( *keys )
results = @stored.values_at( *keys ).map{ |t| Time.now - t }
results.length == 1 ? results.first : results
end
def last_access( *keys )
results = @access.values_at( *keys )
results.length == 1 ? results.first : results
end
def staleness( *keys )
results = @access.values_at( *keys ).map{ |t| Time.now - t }
results.length == 1 ? results.first : results
end
def discard( *keys )
results = keys.map{ |key|
@stored.delete(key)
@access.delete(key)
@library.delete(key)
}
results.length == 1 ? results.first : results
end
def remove_stale( max_staleness )
now = Time.now
stale_keys = @access.select{ |key,time| (now-time) >
max_staleness }.keys
stale_keys.each{ |key|
@stored.delete( key )
@access.delete( key )
object = @library.delete( key )
yield object if block_given?
}
stale_keys.length
end
def remove_old( max_age )
now = Time.now
stale_keys = @stored.select{ |key,time| (now-time) >
max_age }.keys
stale_keys.each{ |key|
@stored.delete( key )
@access.delete( key )
object = @library.delete( key )
yield object if block_given?
}
stale_keys.length
end
end