class AsyncCache::Store

Attributes

backend[RW]
worker_klass[RW]

Public Class Methods

base_cache_key(locator, block_source) click to toggle source

Build the base part of the cache key with the locator and the digest of the block source. This ensures that if the implementation (block) changes then the cache key will also change.

# File lib/async_cache/store.rb, line 143
def self.base_cache_key(locator, block_source)
  ActiveSupport::Cache.expand_cache_key([
    locator,
    Digest::MD5.hexdigest(block_source)
  ])
end
new(opts = {}) click to toggle source

@param [Hash] opts Initialization options @option opts [Object] :backend The backend that it will read/write

entries to/from

@option opts [Symbol] :worker Shorthand symbol for the worker to use,

options are `:active_job` and `:sidekiq`.

@option ops [Class] :worker_klass Class of the worker to use.

# File lib/async_cache/store.rb, line 18
def initialize(opts = {})
  @worker_klass =
    if opts[:worker_klass]
      opts[:worker_klass]
    elsif opts[:worker]
      AsyncCache::Workers.worker_for_name opts[:worker]
    else
      raise ArgumentError, 'Must have a :worker_klass or :worker option'
    end

  @backend = opts[:backend] || AsyncCache.backend

  # Register ourselves in the array of known store instances
  self.class.stores << self
end
stores() click to toggle source

Global index of store instances

# File lib/async_cache/store.rb, line 8
def self.stores
  @stores ||= []
end

Public Instance Methods

clear() click to toggle source
# File lib/async_cache/store.rb, line 81
def clear
  @worker_klass.clear
end
determine_strategy(has_cached_data:, needs_regen:, synchronous_regen:) click to toggle source
# File lib/async_cache/store.rb, line 85
def determine_strategy(has_cached_data:, needs_regen:, synchronous_regen:)
  case
  when !has_cached_data
    # Not present at all
    :generate
  when needs_regen && synchronous_regen
    # Caller has indicated we should synchronously regenerate
    :generate
  when needs_regen && !worker_klass.has_workers?
    # No workers available to regnerate, so do it ourselves; we'll log a
    # warning message that we can alert on
    AsyncCache.logger.warn "No workers running to handle AsyncCache jobs"
    :generate
  when needs_regen
    :enqueue
  else
    :current
  end
end
enqueue_generation(key:, version:, expires_in:, block_source:, arguments:) click to toggle source
# File lib/async_cache/store.rb, line 118
def enqueue_generation(key:, version:, expires_in:, block_source:, arguments:)
  worker_klass.enqueue_async_job(
    key: key,
    version: version,
    expires_in: expires_in,
    block: block_source,
    arguments: arguments
  )
end
fetch(locator, version, options = {}, &block) click to toggle source

@param [String] locator The constant locator for the entry in the cache @param [Fixnum] version Version of the value identified by that locator @param [Hash] options @yield [*arguments in options] Called if entry out-of-date

# File lib/async_cache/store.rb, line 38
def fetch(locator, version, options = {}, &block)
  options = options.dup # Duplicate to avoid side effects
  version = version.to_i # Versions must *always* be convertible to integers

  # Expires-in must be an integer if present, nil if not
  expires_in = options[:expires_in] ? options[:expires_in].to_i : nil

  block_source = block.to_source
  block_arguments = check_arguments(options.delete(:arguments) || [])

  # Serialize arguments into the full cache key
  key = ActiveSupport::Cache.expand_cache_key([
    Store.base_cache_key(locator, block_source),
    block_arguments
  ].flatten)

  cached_data, cached_version = @backend.read key

  strategy = determine_strategy(
    has_cached_data: !!cached_data,
    needs_regen: version > (cached_version || 0),
    synchronous_regen: options[:synchronous_regen]
  )

  return cached_data if strategy == :current

  context = {
    key: key,
    version: version,
    expires_in: expires_in,
    block_source: block_source,
    arguments: block_arguments
  }

  case strategy
  when :generate
    return generate_and_cache context
  when :enqueue
    enqueue_generation context
    return cached_data
  end
end
generate_and_cache(key:, version:, expires_in:, block_source:, arguments:) click to toggle source
# File lib/async_cache/store.rb, line 105
def generate_and_cache(key:, version:, expires_in:, block_source:, arguments:)
  # Mimic the destruction-of-scope behavior of the worker in development
  # so it will *fail* for developers if they try to depend upon scope
  block = eval(block_source)

  data = block.call(*arguments)

  entry = [data, version]
  @backend.write key, entry, :expires_in => expires_in

  return data
end
inspect() click to toggle source
# File lib/async_cache/store.rb, line 128
def inspect
  pointer_format = '0x%014x'
  pointer = Kernel.sprintf pointer_format, self.object_id * 2
  backend_pointer = Kernel.sprintf pointer_format, @backend.object_id * 2

  '#<' + [
    "#{self.class.name}:#{pointer} ",
    "@worker_klass=#{@worker_klass.name}, ",
    "@backend=#<#{@backend.class.name}:#{backend_pointer}>"
  ].join('') + '>'
end

Private Instance Methods

check_arguments(arguments) click to toggle source

Ensures the arguments are primitives.

# File lib/async_cache/store.rb, line 153
def check_arguments arguments
  arguments.each_with_index do |argument, index|
    next if argument.is_a? Numeric
    next if argument.is_a? String
    next if argument.is_a? Symbol
    next if argument.is_a? Hash
    next if argument.is_a? NilClass
    next if argument.is_a? TrueClass
    next if argument.is_a? FalseClass

    raise ArgumentError, "Cannot send complex data for block argument #{index + 1}: #{argument.class.name}"
  end

  arguments
end