class Zache

It is a very simple thread-safe in-memory cache with an ability to expire keys automatically, when their lifetime is over. Use it like this:

require 'zache'
zache = Zache.new
# Expires in 5 minutes
v = zache.get(:count, lifetime: 5 * 60) { expensive() }

For more information read README file.

Author

Yegor Bugayenko (yegor256@gmail.com)

Copyright

Copyright © 2018 Yegor Bugayenko

License

MIT

Public Class Methods

new(sync: true, dirty: false) click to toggle source

Makes a new object of the cache. “sync” is whether the hash is thread-safe (`true`) or not (`false`); it is recommended to leave this parameter untouched, unless you really know what you are doing.

If the dirty argument is set to true, a previously calculated result will be returned if it exists and is already expired.

# File lib/zache.rb, line 76
def initialize(sync: true, dirty: false)
  @hash = {}
  @sync = sync
  @dirty = dirty
  @mutex = Mutex.new
end

Public Instance Methods

clean() click to toggle source

Remove keys that are expired.

# File lib/zache.rb, line 178
def clean
  synchronized { @hash.delete_if { |_key, value| expired?(value) } }
end
exists?(key, dirty: false) click to toggle source

Checks whether the value exists in the cache by the provided key. Returns TRUE if the value is here. If the key is already expired in the hash, it will be removed by this method and the result will be FALSE.

# File lib/zache.rb, line 118
def exists?(key, dirty: false)
  rec = @hash[key]
  if expired?(key) && !dirty && !@dirty
    @hash.delete(key)
    rec = nil
  end
  !rec.nil?
end
expired?(key) click to toggle source

Checks whether the key exists in the cache and is expired. If the key is absent FALSE is returned.

# File lib/zache.rb, line 129
def expired?(key)
  rec = @hash[key]
  !rec.nil? && rec[:start] < Time.now - rec[:lifetime]
end
get(key, lifetime: 2**32, dirty: false) { || ... } click to toggle source

Gets the value from the cache by the provided key. If the value is not found in the cache, it will be calculated via the provided block. If the block is not given, an exception will be raised (unless dirty is set to true). The lifetime must be in seconds. The default lifetime is huge, which means that the key will never be expired.

If the dirty argument is set to true, a previously calculated result will be returned if it exists and is already expired.

# File lib/zache.rb, line 97
def get(key, lifetime: 2**32, dirty: false)
  if block_given?
    if (dirty || @dirty) && locked? && expired?(key) && @hash.key?(key)
      return @hash[key][:value]
    end
    synchronized { calc(key, lifetime) { yield } }
  else
    rec = @hash[key]
    if expired?(key)
      return rec[:value] if dirty || @dirty
      @hash.delete(key)
      rec = nil
    end
    raise 'The key is absent in the cache' if rec.nil?
    rec[:value]
  end
end
locked?() click to toggle source

Is cache currently locked doing something?

# File lib/zache.rb, line 142
def locked?
  @mutex.locked?
end
mtime(key) click to toggle source

Returns the modification time of the key, if it exists. If not, current time is returned.

# File lib/zache.rb, line 136
def mtime(key)
  rec = @hash[key]
  rec.nil? ? Time.now : rec[:start]
end
put(key, value, lifetime: 2**32) click to toggle source

Put a value into the cache.

# File lib/zache.rb, line 147
def put(key, value, lifetime: 2**32)
  synchronized do
    @hash[key] = {
      value: value,
      start: Time.now,
      lifetime: lifetime
    }
  end
end
remove(key) { || ... } click to toggle source

Removes the value from the hash, by the provied key. If the key is absent and the block is provide, the block will be called.

# File lib/zache.rb, line 159
def remove(key)
  synchronized { @hash.delete(key) { yield if block_given? } }
end
remove_all() click to toggle source

Remove all keys from the cache.

# File lib/zache.rb, line 164
def remove_all
  synchronized { @hash = {} }
end
remove_by() { |k| ... } click to toggle source

Remove all keys that match the block.

# File lib/zache.rb, line 169
def remove_by
  synchronized do
    @hash.keys.each do |k|
      @hash.delete(k) if yield(k)
    end
  end
end
size() click to toggle source

Total number of keys currently in cache.

# File lib/zache.rb, line 84
def size
  @hash.size
end

Private Instance Methods

calc(key, lifetime) { || ... } click to toggle source
# File lib/zache.rb, line 184
def calc(key, lifetime)
  rec = @hash[key]
  rec = nil if expired?(key)
  if rec.nil?
    @hash[key] = {
      value: yield,
      start: Time.now,
      lifetime: lifetime
    }
  end
  @hash[key][:value]
end
synchronized() { || ... } click to toggle source
# File lib/zache.rb, line 197
def synchronized
  if @sync
    @mutex.synchronize do
      # I don't know why, but if you remove this line, the tests will
      # break. It seems to me that there is a bug in Ruby. Let's try to
      # fix it or find a workaround and remove this line.
      sleep 0.00001
      yield
    end
  else
    yield
  end
end