class ProstoCache::ProstoModelCache

cache itself, contains pretty much all the logic

Constants

MAX_CACHE_LIFE

Attributes

accessor_keys[R]
cache[RW]
model_class[R]
signature[RW]
sort_keys[R]
validated_at[RW]

Public Class Methods

fail_on_missing_value?(litmus) click to toggle source
# File lib/prosto_cache/prosto_model_cache.rb, line 52
def self.fail_on_missing_value?(litmus)
  case litmus
  when Symbol
    true
  when String
    false
  else
    raise ArgumentError, "Unknown type of cache key #{litmus.inspect}"
  end
end
new(model_class, accessor_keys, sort_keys) click to toggle source
# File lib/prosto_cache/prosto_model_cache.rb, line 65
def initialize(model_class, accessor_keys, sort_keys)
  raise ArgumentError, "No model class provided" unless model_class

  @model_class = model_class
  @accessor_keys = [*(accessor_keys || :name)]
  @sort_keys = sort_keys ? [*(sort_keys)] : @accessor_keys
end

Public Instance Methods

[](*keys) click to toggle source
# File lib/prosto_cache/prosto_model_cache.rb, line 77
def [](*keys)
  unless keys.length == accessor_keys.length
    raise BadCacheKeyError, "Cached accessed by #{keys.length} keys, expected #{accessor_keys.length}"
  end

  keys.zip((1..keys.length)).inject(safe_cache) do |memo, (key, index)|
    value = memo[key]
    unless value
      if ProstoModelCache.fail_on_missing_value?(keys.last)
        raise BadCacheKeyError, key
      else
        value = ProstoHash.new unless index == keys.length
      end
    end

    value
  end
end
invalidate() click to toggle source
# File lib/prosto_cache/prosto_model_cache.rb, line 73
def invalidate
  self.cache = self.signature = self.validated_at = nil
end
keys() click to toggle source
# File lib/prosto_cache/prosto_model_cache.rb, line 96
def keys
  safe_cache.keys
end
values() click to toggle source
# File lib/prosto_cache/prosto_model_cache.rb, line 100
def values
  safe_cache.values
end

Private Instance Methods

build_cache(objects, attributes=[]) click to toggle source
# File lib/prosto_cache/prosto_model_cache.rb, line 147
def build_cache(objects, attributes=[])
  attributes = [*attributes]
  if attributes.empty?
    # terminal case
    raise BadCacheValuesError, "No cache entry found" if objects.nil? || objects.empty?
    raise BadCacheValuesError, "Non deterministic search result, more then one cache entry found" if objects.size > 1
    return objects.first
  else
    reduced_attributes = attributes.dup
    attribute = reduced_attributes.delete_at(0).to_sym
    # first, bucketize to reduce problem's complexity
    array_map = objects.each_with_object({}) do |o, memo|
      key = o.public_send(attribute).to_s
      memo[key] ||= []
      memo[key] << o
    end
    # second, recurse and build cache from those reduced buckets!
    array_map.each_with_object(ProstoHash.new) do |(attr_value, attr_bucket), memo|
      memo[attr_value] = build_cache(attr_bucket, reduced_attributes)
    end
  end
end
load_cache(time, current_cache_signature = nil) click to toggle source
# File lib/prosto_cache/prosto_model_cache.rb, line 133
def load_cache(time, current_cache_signature = nil)
  fail "Can not load already loaded cache" if cache

  current_cache_signature ||= query_cache_signature

  cache_values = model_class.all
  self.cache = build_cache(cache_values, accessor_keys)
  cache.values(sorted_cache_values(cache_values))
  cache.keys(sorted_keys(cache.values))
  self.validated_at = time
  self.signature = current_cache_signature
end
query_cache_signature() click to toggle source
# File lib/prosto_cache/prosto_model_cache.rb, line 188
def query_cache_signature
  raw_result = ActiveRecord::Base.connection.execute(
    "select max(updated_at) as max_updated_at, max(id) as max_id, count(id) as count from #{model_class.table_name}"
  )
  array_result = case raw_result.class.name
  when 'Mysql::Result'
    [].tap { |rows| raw_result.each_hash { |h| rows << h } }
  when 'Mysql2::Result'
    [].tap { |rows| raw_result.each(:as => :hash) { |h| rows << h } }
  when 'PGresult', 'PG::Result'
    raw_result.map(&:to_hash)
  when 'Array' # assuming sqlite
    raw_result
  else
    fail "Result class #{raw_result.class.name} is unsupported"
  end
  array_result.map(&:symbolize_keys).first
end
safe_cache() click to toggle source
# File lib/prosto_cache/prosto_model_cache.rb, line 109
def safe_cache
  time = Time.now.to_i

  if cache && validated_at < time - MAX_CACHE_LIFE
    current_cache_signature = validate_cache_signature(time)
  end

  unless cache
    load_cache(time, current_cache_signature)
  end

  cache
end
sorted_cache_values(cache_values) click to toggle source
# File lib/prosto_cache/prosto_model_cache.rb, line 170
def sorted_cache_values(cache_values)
  cache_values.sort_by { |o|
    sort_keys.inject('') { |memo, k|
      memo << o.public_send(k)
    }
  }
end
sorted_keys(cache_values) click to toggle source
# File lib/prosto_cache/prosto_model_cache.rb, line 178
def sorted_keys(cache_values)
  cache_values.map { |o|
    accessor_keys.inject([]) { |memo, k|
      memo << o.public_send(k).to_sym
    }
  }.tap { |rtn|
    rtn.flatten! if accessor_keys.length == 1
  }
end
validate_cache_signature(time) click to toggle source
# File lib/prosto_cache/prosto_model_cache.rb, line 123
def validate_cache_signature(time)
  query_cache_signature.tap { |current_cache_signature|
    if current_cache_signature == signature
      self.validated_at = time
    else
      invalidate
    end
  }
end