Class: AsyncCache::Store

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

Instance Attribute Summary collapse

Class Method 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.



18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# 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

Instance Attribute Details

#backendObject

Returns the value of attribute backend.



5
6
7
# File 'lib/async_cache/store.rb', line 5

def backend
  @backend
end

#worker_klassObject

Returns the value of attribute worker_klass.



5
6
7
# File 'lib/async_cache/store.rb', line 5

def worker_klass
  @worker_klass
end

Class Method Details

.base_cache_key(locator, block_source) ⇒ Object

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.



143
144
145
146
147
148
# 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

.storesObject

Global index of store instances



8
9
10
# File 'lib/async_cache/store.rb', line 8

def self.stores
  @stores ||= []
end

Instance Method Details

#check_arguments(arguments) ⇒ Object (private)

Ensures the arguments are primitives.



153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
# 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

#clearObject



81
82
83
# File 'lib/async_cache/store.rb', line 81

def clear
  @worker_klass.clear
end

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



85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
# 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:) ⇒ Object



118
119
120
121
122
123
124
125
126
# 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 = {}) {|*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



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
68
69
70
71
72
73
74
75
76
77
78
79
# 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:) ⇒ Object



105
106
107
108
109
110
111
112
113
114
115
116
# 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

#inspectObject



128
129
130
131
132
133
134
135
136
137
138
# 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