class RingCache

Constants

VERSION

Attributes

capacity[R]
target_hit_rate[R]

Public Class Methods

new(options = {}) click to toggle source
# File lib/ring_cache.rb, line 9
def initialize(options = {})
  @duplicate_on_store = options.fetch(:duplicate_on_store, false)
  @duplicate_on_retrieve = options.fetch(:duplicate_on_retrieve, false)

  execute_on_retrieve = options.fetch(:execute_on_retrieve, [])
  @execute_on_retrieve = execute_on_retrieve.kind_of?(Array) ? execute_on_retrieve : [execute_on_retrieve]

  @capacity = options.fetch(:capacity, nil)
  @target_hit_rate = options.fetch(:target_hit_rate, nil)
  unless @target_hit_rate.nil? or (@target_hit_rate > 0.0 and @target_hit_rate < 1.0)
    raise ArgumentError, 'Invalid target_hit_rate'
  end

  reset
end

Public Instance Methods

evict(key) click to toggle source
# File lib/ring_cache.rb, line 25
def evict(key)
  if @cache.has_key?(key)
    @access_time_index.delete([@cache[key][:last_accessed_at], key])
    @cache.delete(key)
    true
  else
    false
  end
end
fetch(key, options = {}, &block) click to toggle source
# File lib/ring_cache.rb, line 35
def fetch(key, options = {}, &block)
  cache_nil = options.fetch(:cache_nil, true)

  unless (data = read(key))
    data = catch(:dont_cache) do
      data_to_cache = block.call
      write(key, data_to_cache) unless data_to_cache.nil? and !cache_nil
      data_to_cache
    end
  end
  data
end
has_key?(key) click to toggle source
# File lib/ring_cache.rb, line 48
def has_key?(key)
  @cache.has_key?(key)
end
hit_rate() click to toggle source
# File lib/ring_cache.rb, line 52
def hit_rate
  (@access_count > 0) ? (@hit_count / @access_count.to_f) : 0.0
end
last_access(key) click to toggle source
# File lib/ring_cache.rb, line 56
def last_access(key)
  has_key?(key) ? @cache[key][:last_accessed_at] : nil
end
read(key) click to toggle source
# File lib/ring_cache.rb, line 93
def read(key)
  read!(key)
rescue KeyNotFoundError
  return nil
end
read!(key) click to toggle source
# File lib/ring_cache.rb, line 60
def read!(key)
  @access_count += 1

  if @cache.has_key?(key)
    access_time = Time.now
    @access_time_index.delete([@cache[key][:last_accessed_at], key])
    @access_time_index << [access_time, key]
    @cache[key][:last_accessed_at] = access_time

    @hit_count += 1

    data = @cache[key][:data]
    data = data.dup if @duplicate_on_retrieve and !data.nil?

    unless @execute_on_retrieve.empty? or data.nil?
      @execute_on_retrieve.each do |method|
        method = method.to_sym
        if data.respond_to?(method)
          data.send(method)
        elsif data.kind_of?(Enumerable) and data.all? { |d| d.respond_to?(method) }
          data.each { |d| d.send(method) }
        else
          raise RuntimeError, "Retrieved data does not respond to #{method.inspect}"
        end
      end
    end

    data
  else
    fail KeyNotFoundError, "Cache does not have content indexed by #{key}"
  end
end
reset() click to toggle source
# File lib/ring_cache.rb, line 99
def reset
  @cache = {}
  @access_time_index = SortedSet.new
  @access_count = 0
  @hit_count = 0
  true
end
size() click to toggle source
# File lib/ring_cache.rb, line 107
def size
  @cache.size
end
write(key, data) click to toggle source
# File lib/ring_cache.rb, line 111
def write(key, data)
  unless evict(key)
    evict_oldest if must_evict?
  end
  data = data.dup if @duplicate_on_store and !data.nil?
  access_time = Time.now
  @cache[key] = { last_accessed_at: access_time, data: data }
  @access_time_index << [access_time, key]
  true
end

Private Instance Methods

evict_oldest() click to toggle source
# File lib/ring_cache.rb, line 124
def evict_oldest
  access_time_index_entry = @access_time_index.first
  @cache.delete(access_time_index_entry[1])
  @access_time_index.delete(access_time_index_entry)
end
must_evict?() click to toggle source
# File lib/ring_cache.rb, line 130
def must_evict?
  (capacity and size >= capacity) or (target_hit_rate and hit_rate >= target_hit_rate)
end