class Ramaze::LRUHash

A Hash-alike LRU cache that provides fine-grained control over content restrictions.

It allows you to set:

Differences to the original implementation include:

Note that due to calculating object size with Marshal, you might have to do some evaluation as to how large your values will be when marshaled, for example a String will have String#size + 10. This differs from object to object and between versions of Marshal, so be generous.

Copyright (C) 2002 Yoshinori K. Okuji <okuji@enbug.org> Copyright © 2009 Michael Fellinger <manveru@rubyists.com>

You may redistribute it and/or modify it under the same terms as Ruby.

Constants

CacheObject
KeyError

On 1.8 we raise IndexError, on 1.9 we raise KeyError

VERSION

Public Class Methods

new(options = {}, &hook) click to toggle source
# File lib/ramaze/snippets/ramaze/lru_hash.rb, line 34
def initialize(options = {}, &hook)
  self.max_value  = options[:max_value]
  self.max_total  = options[:max_total]
  self.max_count  = options[:max_count]
  self.expiration = options[:expiration]

  avoid_insane_options

  self.hook = hook

  self.objs = {}
  self.list = []

  self.total_size = 0
  self.hits = self.misses = 0
end

Public Instance Methods

[](key) click to toggle source
# File lib/ramaze/snippets/ramaze/lru_hash.rb, line 120
def [](key)
  expire

  unless objs.key?(key)
    self.misses += 1
    return
  end

  obj = objs[key]
  obj.atime = Time.now.to_i

  list.delete_if{|list_key| key == list_key }
  list << key

  self.hits += 1
  obj.content
end
[]=(key, obj) click to toggle source
# File lib/ramaze/snippets/ramaze/lru_hash.rb, line 138
def []=(key, obj)
  expire

  invalidate key if key?(key)

  size = Marshal.dump(obj).size

  if max_value && max_value < max_total
    warn "%p isn't cached because it exceeds max_value %p" % [obj, max_value]
    return obj
  end

  if max_value.nil? && max_total && max_total < size
    warn "%p isn't cached because it exceeds max_total: %p" % [obj, max_total]
    return obj
  end

  invalidate list.first if max_count && max_count == list.size

  self.total_size += size

  if max_total
    invalidate list.first until total_size < max_total
  end

  objs[key] = CacheObject.new(obj, size, Time.now.to_i)
  list << key

  obj
end
clear() click to toggle source
# File lib/ramaze/snippets/ramaze/lru_hash.rb, line 102
def clear
  objs.each{|key, obj| hook.call(key, obj) } if hook
  objs.clear
  list.clear
  self.total_size = 0
end
Also aliased as: invalidate_all
delete(key) click to toggle source
# File lib/ramaze/snippets/ramaze/lru_hash.rb, line 88
def delete(key)
  return unless objs.key?(key)
  obj = objs[key]

  hook.call(key, obj.content) if hook
  self.total_size -= obj.size
  objs.delete key

  list.delete_if{|list_key| key == list_key }

  obj.content
end
Also aliased as: invalidate
each_key() { |key| ... } click to toggle source
# File lib/ramaze/snippets/ramaze/lru_hash.rb, line 179
def each_key(&block)
  return enum_for(:each_key) unless block_given?
  objs.each_key{|key| yield key }
  self
end
each_pair() { |key, content| ... } click to toggle source
# File lib/ramaze/snippets/ramaze/lru_hash.rb, line 173
def each_pair
  return enum_for(:each_pair) unless block_given?
  objs.each{|key, obj| yield key, obj.content }
  self
end
each_value() { |content| ... } click to toggle source
# File lib/ramaze/snippets/ramaze/lru_hash.rb, line 185
def each_value
  return enum_for(:each_value) unless block_given?
  objs.each_value{|obj| yield obj.content }
  self
end
empty?() click to toggle source
# File lib/ramaze/snippets/ramaze/lru_hash.rb, line 191
def empty?
  objs.empty?
end
expire() click to toggle source
# File lib/ramaze/snippets/ramaze/lru_hash.rb, line 110
def expire
  return unless expiration
  now = Time.now.to_i

  list.each_with_index do |key, index|
    break unless (objs[key].atime + expiration) <= now
    invalidate key
  end
end
fetch(key, default = (p_default = true; nil)) { |key| ... } click to toggle source

Note that this method diverges from the default behaviour of the Ruby Hash. If the cache doesn't find content for the given key, it will store the given default instead. Optionally it also takes a block, the return value of the block is then stored and returned.

@example

lru = LRUHash.new
lru.fetch(:a) # => KeyError: key not found: :a
lru.fetch(:a, :b) # => :b
lru.fetch(:a) # => :b
lru.fetch(:c){|key| key.to_s } # => 'c'
lru.fetch(:c) # => 'c'
# File lib/ramaze/snippets/ramaze/lru_hash.rb, line 208
def fetch(key, default = (p_default = true; nil))
  if key?(key)
    value = self[key]
  elsif p_default.nil?
    value = self[key] = default
  elsif block_given?
    value = self[key] = yield(key)
  else
    raise KeyError, "key not found: %p" % [key]
  end

  value
end
index(given_value) click to toggle source
# File lib/ramaze/snippets/ramaze/lru_hash.rb, line 63
def index(given_value)
  objs.each do |key, obj|
    return key if given_value == obj.content
  end

  nil
end
invalidate(key)
Alias for: delete
invalidate_all()
Alias for: clear
key?(key) click to toggle source
# File lib/ramaze/snippets/ramaze/lru_hash.rb, line 51
def key?(key)
  objs.key?(key)
end
keys() click to toggle source
# File lib/ramaze/snippets/ramaze/lru_hash.rb, line 71
def keys
  objs.keys
end
length()
Alias for: size
size() click to toggle source
# File lib/ramaze/snippets/ramaze/lru_hash.rb, line 75
def size
  objs.size
end
Also aliased as: length
statistics() click to toggle source
# File lib/ramaze/snippets/ramaze/lru_hash.rb, line 222
def statistics
  {:size => total_size, :count => list.size, :hits => hits, :misses => misses}
end
store(key, value) click to toggle source
# File lib/ramaze/snippets/ramaze/lru_hash.rb, line 169
def store(key, value)
  self[key] = value
end
to_hash() click to toggle source
# File lib/ramaze/snippets/ramaze/lru_hash.rb, line 80
def to_hash
  objs.dup
end
value?(given_value) click to toggle source
# File lib/ramaze/snippets/ramaze/lru_hash.rb, line 55
def value?(given_value)
  objs.each do |key, obj|
    return true if given_value == obj.content
  end

  false
end
values() click to toggle source
# File lib/ramaze/snippets/ramaze/lru_hash.rb, line 84
def values
  objs.map{|key, obj| obj.content }
end

Private Instance Methods

avoid_insane_options() click to toggle source

Sanity checks.

# File lib/ramaze/snippets/ramaze/lru_hash.rb, line 229
def avoid_insane_options
  if (max_value && max_total) && max_value > max_total
    raise ArgumentError, "max_value exceeds max_total (#{max_value} > #{max_total})"
  end
  if max_value && max_value <= 0
    raise ArgumentError, "invalid max_value `#{max_value}'"
  end
  if max_total && max_total <= 0
    raise ArgumentError, "invalid max_total `#{max_total}'"
  end
  if max_count && max_count <= 0
    raise ArgumentError, "invalid max_count `#{max_count}'"
  end
  if expiration && expiration <= 0
    raise ArgumentError, "invalid expiration `#{expiration}'"
  end
end