Class: AsyncCache::Store

Inherits:
Object
  • Object
show all
Defined in:
lib/async_cache/store.rb

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(opts = {}) ⇒ Store

Returns a new instance of Store.

Parameters:

  • opts (Hash) (defaults to: {})

    Initialization options

  • ops (Hash)

    a customizable set of options

Options Hash (opts):

  • :backend (Object)

    The backend that it will read/write entries to/from

  • :worker (Symbol)

    Shorthand symbol for the worker to use, options are :active_job and :sidekiq.



11
12
13
14
15
16
17
18
19
20
21
22
# File 'lib/async_cache/store.rb', line 11

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
end

Instance Attribute Details

#backendObject

Returns the value of attribute backend.



3
4
5
# File 'lib/async_cache/store.rb', line 3

def backend
  @backend
end

#worker_klassObject

Returns the value of attribute worker_klass.



3
4
5
# File 'lib/async_cache/store.rb', line 3

def worker_klass
  @worker_klass
end

Instance Method Details

#check_arguments(arguments) ⇒ Object (private)

Ensures the arguments are primitives.



117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
# File 'lib/async_cache/store.rb', line 117

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

#determine_strategy(has_cached_data:, needs_regen:, synchronous_regen:) ⇒ Object



69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
# File 'lib/async_cache/store.rb', line 69

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:, arguments:) ⇒ Object



104
105
106
107
108
109
110
111
112
# File 'lib/async_cache/store.rb', line 104

def enqueue_generation(key:, version:, expires_in:, block:, arguments:)
  worker_klass.enqueue_async_job(
    key:        key,
    version:    version,
    expires_in: expires_in,
    block:      block.to_source,
    arguments:  arguments
  )
end

#fetch(locator, version, options = {}) {|*arguments in options[:arguments]| ... } ⇒ Object

Parameters:

  • locator (String)

    The constant locator for the entry in the cache

  • version (Fixnum)

    Version of the value identified by that locator

  • options (Hash) (defaults to: {})

Yields:

  • (*arguments in options[:arguments])

    Called if entry out-of-date



28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
# File 'lib/async_cache/store.rb', line 28

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_arguments = check_arguments(options.delete(:arguments) || [])

  # Serialize arguments into the full cache key
  key = ActiveSupport::Cache.expand_cache_key Array.wrap(locator) + block_arguments

  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]
  )

  context = {
    :key        => key,
    :version    => version,
    :expires_in => expires_in,
    :block      => block,
    :arguments  => block_arguments
  }

  case strategy
  when :generate
    return generate_and_cache context

  when :enqueue
    enqueue_generation context
    return cached_data

  when :current
    return cached_data
  end
end

#generate_and_cache(key:, version:, expires_in:, block:, arguments:) ⇒ Object



89
90
91
92
93
94
95
96
97
98
99
100
101
102
# File 'lib/async_cache/store.rb', line 89

def generate_and_cache(key:, version:, expires_in:, block:, arguments:)
  block_source = block.to_source

  # 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