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.

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.



16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# File 'lib/async_cache/store.rb', line 16

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.



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

Class Method Details

.storesObject

Global index of store instances



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

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

Instance Method Details

#check_arguments(arguments) ⇒ Object (private)

Ensures the arguments are primitives.



141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
# File 'lib/async_cache/store.rb', line 141

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



77
78
79
# File 'lib/async_cache/store.rb', line 77

def clear
  @worker_klass.clear
end

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



81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
# File 'lib/async_cache/store.rb', line 81

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



116
117
118
119
120
121
122
123
124
# File 'lib/async_cache/store.rb', line 116

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

Yields:

  • (*arguments in options[:arguments])

    Called if entry out-of-date



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
68
69
70
71
72
73
74
75
# File 'lib/async_cache/store.rb', line 36

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



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

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

#inspectObject



126
127
128
129
130
131
132
133
134
135
136
# File 'lib/async_cache/store.rb', line 126

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