class Sfn::Cache

Data caching helper

Attributes

key[R]

@return [String] custom key for this cache

Public Class Methods

apply_limit(kind, seconds = nil) click to toggle source

Set/get time limit on data type

@param kind [String, Symbol] data type @param seconds [Integer] return [Integer] seconds

# File lib/sfn/cache.rb, line 51
def apply_limit(kind, seconds = nil)
  @apply_limit ||= {}
  if seconds
    @apply_limit[kind.to_sym] = seconds.to_i
  end
  @apply_limit[kind.to_sym].to_i
end
configure(type, args = {}) click to toggle source

Configure the caching approach to use

@param type [Symbol] :redis or :local @param args [Hash] redis connection arguments if used

# File lib/sfn/cache.rb, line 14
def configure(type, args = {})
  type = type.to_sym
  case type
  when :redis
    begin
      require "redis-objects"
    rescue LoadError
      $stderr.puts "The `redis-objects` gem is required for Cache support!"
      raise
    end
    @_pid = Process.pid
    Redis::Objects.redis = Redis.new(args)
  when :local
  else
    raise TypeError.new("Unsupported caching type: #{type}")
  end
  enable(type)
end
default_limits() click to toggle source

@return [Hash] default limits

# File lib/sfn/cache.rb, line 60
def default_limits
  (@apply_limit || {}).dup
end
enable(type) click to toggle source

Set enabled caching type

@param type [Symbol] @return [Symbol]

# File lib/sfn/cache.rb, line 37
def enable(type)
  @type = type.to_sym
end
new(key) click to toggle source

Create new instance

@param key [String, Array]

# File lib/sfn/cache.rb, line 79
def initialize(key)
  if key.respond_to?(:sort)
    key = key.flatten if key.respond_to?(:flatten)
    key = key.map(&:to_s).sort
  end
  @key = Digest::SHA256.hexdigest(key.to_s)
  @apply_limit = self.class.default_limits
end
redis_ping!() click to toggle source

Ping the redis connection and reconnect if dead

# File lib/sfn/cache.rb, line 65
def redis_ping!
  if (@_pid && @_pid != Process.pid) || !Redis::Objects.redis.connected?
    Redis::Objects.redis.client.reconnect
    @_pid = Process.pid
  end
end
type() click to toggle source

@return [Symbol] type of caching enabled

# File lib/sfn/cache.rb, line 42
def type
  @type || :local
end

Public Instance Methods

[](name) click to toggle source

Fetch data

@param name [String, Symbol] @return [Object, NilClass]

# File lib/sfn/cache.rb, line 216
def [](name)
  if kind = registry[name.to_s]
    get_storage(self.class.type, kind, name)
  else
    nil
  end
end
[]=(key, val) click to toggle source

Set data

@param key [Object] @param val [Object] @note this will never work, thus you should never use it

# File lib/sfn/cache.rb, line 229
def []=(key, val)
  raise "Setting backend data is not allowed"
end
apply_limit(kind, seconds = nil) click to toggle source

Apply time limit for data type

@param kind [String, Symbol] data type @param seconds [Integer] return [Integer]

# File lib/sfn/cache.rb, line 247
def apply_limit(kind, seconds = nil)
  @apply_limit ||= {}
  if seconds
    @apply_limit[kind.to_sym] = seconds.to_i
  end
  @apply_limit[kind.to_sym].to_i
end
clear!(*args) { || ... } click to toggle source

Clear data

@param args [Symbol] list of names to delete @return [TrueClass] @note clears all data if no names provided

# File lib/sfn/cache.rb, line 108
def clear!(*args)
  internal_lock do
    args = registry.keys if args.empty?
    args.each do |key|
      value = self[key]
      if value.respond_to?(:clear)
        value.clear
      elsif value.respond_to?(:value)
        value.value = nil
      end
      registry.delete(key)
    end
    yield if block_given?
  end
  true
end
get_local_storage(data_type, full_name, args = {}) click to toggle source

Fetch item from local storage

@param data_type [Symbol] @param full_name [Symbol] @param args [Hash] @return [Object] @todo make proper singleton for local storage

# File lib/sfn/cache.rb, line 184
def get_local_storage(data_type, full_name, args = {})
  @storage ||= {}
  @storage[full_name] ||= case data_type.to_sym
                          when :array
                            []
                          when :hash
                            {}
                          when :value
                            LocalValue.new
                          when :lock
                            LocalLock.new(full_name, {:expiration => 60, :timeout => 0.1}.merge(args))
                          when :stamped
                            Stamped.new(full_name.sub("#{key}_", "").to_sym, get_local_storage(:value, full_name), self)
                          else
                            raise TypeError.new("Unsupported caching data type encountered: #{data_type}")
                          end
end
get_redis_storage(data_type, full_name, args = {}) click to toggle source

Fetch item from redis storage

@param data_type [Symbol] @param full_name [Symbol] @param args [Hash] @return [Object]

# File lib/sfn/cache.rb, line 159
def get_redis_storage(data_type, full_name, args = {})
  self.class.redis_ping!
  case data_type.to_sym
  when :array
    Redis::List.new(full_name, {:marshal => true}.merge(args))
  when :hash
    Redis::HashKey.new(full_name)
  when :value
    Redis::Value.new(full_name, {:marshal => true}.merge(args))
  when :lock
    Redis::Lock.new(full_name, {:expiration => 60, :timeout => 0.1}.merge(args))
  when :stamped
    Stamped.new(full_name.sub("#{key}_", "").to_sym, get_redis_storage(:value, full_name), self)
  else
    raise TypeError.new("Unsupported caching data type encountered: #{data_type}")
  end
end
get_storage(store_type, data_type, name, args = {}) click to toggle source

Fetch item from storage

@param store_type [Symbol] @param data_type [Symbol] @param name [Symbol] name of data @param args [Hash] options for underlying storage @return [Object]

# File lib/sfn/cache.rb, line 132
def get_storage(store_type, data_type, name, args = {})
  full_name = "#{key}_#{name}"
  result = nil
  case store_type.to_sym
  when :redis
    result = get_redis_storage(data_type, full_name.to_s, args)
  when :local
    @_local_cache ||= {}
    unless @_local_cache[full_name.to_s]
      @_local_cache[full_name.to_s] = get_local_storage(data_type, full_name.to_s, args)
    end
    result = @_local_cache[full_name.to_s]
  else
    raise TypeError.new("Unsupported caching storage type encountered: #{store_type}")
  end
  unless full_name == "#{key}_registry_#{key}"
    registry[name.to_s] = data_type
  end
  result
end
init(name, kind, args = {}) click to toggle source

Initialize a new data type

@param name [Symbol] name of data @param kind [Symbol] data type @param args [Hash] options for data type

# File lib/sfn/cache.rb, line 93
def init(name, kind, args = {})
  get_storage(self.class.type, kind, name, args)
  true
end
internal_lock() { || ... } click to toggle source

Execute block within internal lock

@return [Object] result of yield @note for internal use

# File lib/sfn/cache.rb, line 206
def internal_lock
  get_storage(self.class.type, :lock, :internal_access, :timeout => 20, :expiration => 120).lock do
    yield
  end
end
locked_action(lock_name, raise_on_locked = false) { || ... } click to toggle source

Perform action within lock

@param lock_name [String, Symbol] name of lock @param raise_on_locked [TrueClass, FalseClass] raise execption if lock wait times out @return [Object] result of yield

# File lib/sfn/cache.rb, line 260
def locked_action(lock_name, raise_on_locked = false)
  begin
    self[lock_name].lock do
      yield
    end
  rescue => e
    if e.class.to_s.end_with?("Timeout")
      raise if raise_on_locked
    else
      raise
    end
  end
end
registry() click to toggle source

@return [Hash] data registry

# File lib/sfn/cache.rb, line 99
def registry
  get_storage(self.class.type, :hash, "registry_#{key}")
end
time_check_allow?(key, stamp) click to toggle source

Check if cache time has expired

@param key [String, Symbol] value key @param stamp [Time, Integer] @return [TrueClass, FalseClass]

# File lib/sfn/cache.rb, line 238
def time_check_allow?(key, stamp)
  Time.now.to_i - stamp.to_i > apply_limit(key)
end