module Cachable

Constants

VERSION

Public Class Methods

configuration() click to toggle source
# File lib/cachable/configuration.rb, line 19
def self.configuration
  @config ||= Configuration.new
end
configure() { |configuration| ... } click to toggle source
# File lib/cachable/configuration.rb, line 23
def self.configure
  yield self.configuration
end
redis() click to toggle source
# File lib/cachable/configuration.rb, line 27
def self.redis
  self.configuration.redis
end
unless_cached(action, opts={}) { |record) : send| ... } click to toggle source

Calls unless cached on all records passed action opts. If true will not pull the records in in batches, which increases memory overhead but preserves order. opts. If true will add its own cache, regardless of what the underlying function does opts. If true will add another layer of caching outside opts. If true will not generate (useful for prepopulating the cache with lower overhead) opts If true, and was also true previous times, it will remove the cached batches for the previous batch. This can be very useful when you are frequently adding more records and want to keep the redis memory usage down. Can also pass in all normal unless_cached options

# File lib/cachable.rb, line 59
def self.unless_cached(action, opts={})
  result = []

  iterator = all.find_in_batches
  iterator = all.each_in_order_in_batches if self.respond_to? :each_in_order_in_batches
  iterator = all.each if opts[:slurp]

  partial_key = opts[:key].present? ? opts[:key] : action
  added_key = ''
  added_key = "_#{self.added_redis_key}" if self.respond_to? :added_redis_key

  batch_key_list = []
  record_count = 0

  opts[:skip_cache] = !opts[:force_cache]

  iterator.each do |batch|
    factors = batch.pluck(:id, :updated_at)
    record_count += factors.length if opts[:clear_previous_batch]

    if opts[:cache_batches]
      batch_key = "#{self.to_s.downcase}_#{factors.flatten.join(',')}#{added_key}_#{partial_key}"
      batch_key_list << batch_key if opts[:clear_previous_batch]

      existing_result = Cachable::redis.get batch_key
      if existing_result.present?
        result.concat JSON(existing_result)
        next
      end
    end

    batch_result = factors.map do |id, updated_at|
      key = "#{self.to_s.downcase}_#{id}_#{updated_at.to_i}#{added_key}_#{partial_key}"

      self.unless_cached_base(key, opts) do
        record = self.unscope(:order, :where, :offset).find id

        block_given? ? (yield record) : record.send(action) if record.present?
      end
    end

    result.concat batch_result unless opts[:skip_result]

    if opts[:cache_batches]
      Cachable::redis.set batch_key, JSON(batch_result)
      expiration = opts[:expiration]
      expiration = 15.minutes unless expiration.present?
      Cachable::redis.expire batch_key, expiration unless expiration === false
    end
  end

  if opts[:clear_previous_batch]

    # store the keys for the current batch list
    expiration = opts[:expiration]
    expiration = 15.minutes unless expiration.present?

    gen_key = -> n {
      "#{self.to_s.downcase}_keys_#{n}_#{added_key}_#{partial_key}"
    }
    batch_key = gen_key[record_count]

    Cachable::redis.set(batch_key, JSON(batch_key_list))
    Cachable::redis.expire(batch_key, expiration)

    # delete the keys from the previous batch list
    Cachable::redis.del(gen_key[record_count - 1])
    Cachable::redis.del(gen_key[record_count + 1])
  end

  result
end
unless_cached_base(key, opts={}) { || ... } click to toggle source

Core implementation of unless cached. As well as options from above: opts. If true, will not populate the cache

# File lib/cachable.rb, line 135
def self.unless_cached_base(key, opts={})
  opts[:json] = true if opts[:json].nil?

  cached = Cachable::redis.get(key)
  if cached.present?
    cached = JSON.parse(cached, opts[:json_options]) if opts[:json]

    return cached
  end

  result = yield

  unless opts[:skip_cache]
    if opts[:json]
      Cachable::redis.set(key, JSON.generate(result, opts[:json_options]))
    else
      Cachable::redis.set(key, result)
    end

    expiration = opts[:expiration]
    expiration = 1.day unless expiration.present?
    Cachable::redis.expire(key, expiration) unless expiration === false
  end

  result

end

Public Instance Methods

base_redis_key(opts={}) click to toggle source

Generates the basic redis key from id, updated_at, and whatever else we want

# File lib/cachable.rb, line 12
def base_redis_key(opts={})
  added = ''
  added = "_#{self.added_redis_key}" if self.respond_to? :added_redis_key

  on_previous_changes = opts[:after_commit] && self.previous_changes['updated_at']
  updated_at = on_previous_changes ? self.previous_changes['updated_at'].first : self.updated_at

  "#{self.class.to_s.downcase}_#{self.id}_#{updated_at.to_i}#{added}"
end
delete_from_cache(*keys, **opts) click to toggle source

Clears the given keys from redis

# File lib/cachable.rb, line 23
def delete_from_cache(*keys, **opts)
  base_key = self.base_redis_key opts
  keys.each do |key|
    Cachable::redis.del "#{base_key}_#{key}"
  end
end
purge_cache() click to toggle source

Purges the cache for this record, if the client has defined the purge_cache method

# File lib/cachable.rb, line 31
def purge_cache
  return unless self.respond_to? :tracked_cache_keys

  self.delete_from_cache(*self.tracked_cache_keys, after_commit: true)
end
unless_cached(opts={}) { || ... } click to toggle source

If the is present in the cache, returns that; otherwise generates and caches the result opts. If present, will be added to the base key to generate the full key. Defaults to the name of the caller. opts. If true, will serialize and deserialize the result as json opts. Time for which to cache the result. Defaults to 1 day opts. Options that get passed into json serialization and deserialization

# File lib/cachable.rb, line 42
def unless_cached(opts={})
  partial_key = opts[:key].present? ? opts[:key] : caller.first.match(/`[^']*/).to_s[1..-1]
  key = "#{self.base_redis_key}_#{partial_key}"

  self.class.unless_cached_base(key, opts) do
    yield
  end
end