Class: Readthis::Cache

Inherits:
Object
  • Object
show all
Defined in:
lib/readthis/cache.rb

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(options = {}) ⇒ Cache

Creates a new Readthis::Cache object with the given options.

Examples:

Create a new cache instance

Readthis::Cache.new(namespace: 'cache',
                    redis: { url: 'redis://localhost:6379/0' })

Create a compressed cache instance

Readthis::Cache.new(compress: true, compression_threshold: 2048)

Options Hash (options):

  • :redis (Hash)

    Options that will be passed to the redis connection

  • :compress (Boolean) — default: false

    Enable or disable automatic compression

  • :compression_threshold (Number) — default: 8k

    Minimum string size for compression

  • :expires_in (Number)

    The number of seconds until an entry expires

  • :refresh (Boolean) — default: false

    Automatically refresh key expiration

  • :marshal (Module) — default: Marshal

    Module that responds to `dump` and `load`

  • :namespace (String)

    Prefix used to namespace entries

  • :pool_size (Number) — default: 5

    The number of threads in the pool

  • :pool_timeout (Number) — default: 5

    How long before a thread times out



39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
# File 'lib/readthis/cache.rb', line 39

def initialize(options = {})
  @options = options

  @entity = Readthis::Entity.new(
    marshal:   options.fetch(:marshal, Marshal),
    compress:  options.fetch(:compress, false),
    threshold: options.fetch(:compression_threshold, 1024)
  )

  @pool = ConnectionPool.new(pool_options(options)) do
    Redis.new(options.fetch(:redis, {}))
  end

  @scripts = Readthis::Scripts.new
end

Instance Attribute Details

#entityObject (readonly)

Returns the value of attribute entity



10
11
12
# File 'lib/readthis/cache.rb', line 10

def entity
  @entity
end

#notificationsObject (readonly)

Returns the value of attribute notifications



10
11
12
# File 'lib/readthis/cache.rb', line 10

def notifications
  @notifications
end

#optionsObject (readonly)

Returns the value of attribute options



10
11
12
# File 'lib/readthis/cache.rb', line 10

def options
  @options
end

#poolObject (readonly)

Returns the value of attribute pool



10
11
12
# File 'lib/readthis/cache.rb', line 10

def pool
  @pool
end

#scriptsObject (readonly)

Returns the value of attribute scripts



10
11
12
# File 'lib/readthis/cache.rb', line 10

def scripts
  @scripts
end

Class Method Details

.notificationsObject

Provide a class level lookup of the proper notifications module. Instrumention is expected to occur within applications that have ActiveSupport::Notifications available, but needs to work even when it isn't.



16
17
18
# File 'lib/readthis/cache.rb', line 16

def self.notifications
  ActiveSupport::Notifications if defined?(ActiveSupport::Notifications)
end

Instance Method Details

#clear(_options = nil) ⇒ Object

Clear the entire cache. This flushes the current database, no globbing is applied.

Examples:


cache.clear #=> 'OK'


365
366
367
# File 'lib/readthis/cache.rb', line 365

def clear(_options = nil)
  invoke(:clear, '*', &:flushdb)
end

#decrement(key, amount = 1, options = {}) ⇒ Object

Decrement a key in the store.

If the key doesn't exist it will be initialized at 0. If the key exists but it isn't a Fixnum it will be initialized at 0.

Examples:


cache.write('counter', 20) # => 20
cache.decrement('counter') # => 19
cache.decrement('counter', 2) # => 17


243
244
245
246
247
# File 'lib/readthis/cache.rb', line 243

def decrement(key, amount = 1, options = {})
  invoke(:decrement, key) do |_store|
    alter(key, amount * -1, options)
  end
end

#delete(key, options = {}) ⇒ Object

Delete the value stored at the specified key. Returns `true` if anything was deleted, `false` otherwise.

Examples:


cache.delete('existing-key') # => true
cache.delete('random-key')   # => false


110
111
112
113
114
115
116
# File 'lib/readthis/cache.rb', line 110

def delete(key, options = {})
  namespaced = namespaced_key(key, merged_options(options))

  invoke(:delete, key) do |store|
    store.del(namespaced) > 0
  end
end

#delete_matched(pattern, options = {}) ⇒ Object

Delete all values that match a given pattern. The pattern must be defined using Redis compliant globs. The following examples are borrowed from the `KEYS` documentation:

  • `h?llo` matches hello, hallo and hxllo

  • `h*llo` matches hllo and heeeello

  • `hllo` matches hello and hallo, but not hillo

  • `hllo` matches hallo, hbllo, … but not hello

  • `hllo` matches hallo and hbllo

Note that `delete_matched` does not use the `KEYS` command, making it safe for use in production.

Examples:

Delete all 'cat' keys


cache.delete_matched('*cats') #=> 47
cache.delete_matched('*dogs') #=> 0


140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
# File 'lib/readthis/cache.rb', line 140

def delete_matched(pattern, options = {})
  namespaced = namespaced_key(pattern, merged_options(options))

  invoke(:delete, pattern) do |store|
    cursor  = nil
    count   = options.fetch(:count, 1000)
    deleted = 0

    until cursor == '0'.freeze
      cursor, matched = store.scan(cursor || 0, match: namespaced, count: count)

      if matched.any?
        store.del(*matched)
        deleted += matched.length
      end
    end

    deleted
  end
end

#exist?(key, options = {}) ⇒ Boolean

Returns `true` if the cache contains an entry for the given key.

Examples:


cache.exist?('some-key') # => false
cache.exist?('some-key', namespace: 'cache') # => true


350
351
352
353
354
# File 'lib/readthis/cache.rb', line 350

def exist?(key, options = {})
  invoke(:exist?, key) do |store|
    store.exists(namespaced_key(key, merged_options(options)))
  end
end

#fetch(key, options = {}) {|String| ... } ⇒ Object

Fetches data from the cache, using the given key. If there is data in the cache with the given key, then that data is returned.

If there is no such data in the cache (a cache miss), then `nil` will be returned. However, if a block has been passed, that block will be passed the key and executed in the event of a cache miss. The return value of the block will be written to the cache under the given cache key, and that return value will be returned.

Examples:

Typical


cache.write('today', 'Monday')
cache.fetch('today') # => "Monday"
cache.fetch('city')  # => nil

With a block


cache.fetch('city') do
  'Duckburgh'
end

cache.fetch('city') # => "Duckburgh"

Cache Miss


cache.write('today', 'Monday')
cache.fetch('today', force: true) # => nil

Options Hash (options):

  • :force (Boolean)

    Force a cache miss

Yields:

  • (String)

    Gives a missing key to the block, which is used to generate the missing value



195
196
197
198
199
200
201
202
203
204
205
# File 'lib/readthis/cache.rb', line 195

def fetch(key, options = {})
  options ||= {}
  value = read(key, options) unless options[:force]

  if value.nil? && block_given?
    value = yield(key)
    write(key, value, options)
  end

  value
end

#fetch_multi(keys) ⇒ Object

Fetches multiple keys from the cache using a single call to the server and filling in any cache misses. All read and write operations are executed atomically.

Examples:


cache.fetch_multi('alpha', 'beta') do |key|
  "#{key}-was-missing"
end

cache.fetch_multi('a', 'b', expires_in: 60) do |key|
  key * 2
end

Return all values for the given keys, applying the block to the key when a value is missing.



320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
# File 'lib/readthis/cache.rb', line 320

def fetch_multi(*keys)
  results   = read_multi(*keys)
  extracted = extract_options!(keys)
  missing   = {}

  invoke(:fetch_multi, keys) do |_store|
    results.each do |key, value|
      next unless value.nil?

      value = yield(key)
      missing[key] = value
      results[key] = value
    end
  end

  write_multi(missing, extracted) if missing.any?

  results
end

#increment(key, amount = 1, options = {}) ⇒ Object

Increment a key in the store.

If the key doesn't exist it will be initialized at 0. If the key exists but it isn't a Fixnum it will be initialized at 0.

Examples:


cache.increment('counter') # => 0
cache.increment('counter') # => 1
cache.increment('counter', 2) # => 3


222
223
224
225
226
# File 'lib/readthis/cache.rb', line 222

def increment(key, amount = 1, options = {})
  invoke(:increment, key) do |_store|
    alter(key, amount, options)
  end
end

#read(key, options = {}) ⇒ Object

Fetches data from the cache, using the given key. If there is data in the cache with the given key, then that data is returned. Otherwise, nil is returned.

Examples:


cache.read('missing') # => nil
cache.read('matched') # => 'some value'


67
68
69
70
71
72
73
74
75
76
77
# File 'lib/readthis/cache.rb', line 67

def read(key, options = {})
  options = merged_options(options)

  invoke(:read, key) do |store|
    key = namespaced_key(key, options)

    refresh_entity(key, store, options)

    entity.load(store.get(key))
  end
end

#read_multi(keys) ⇒ Hash

Read multiple values at once from the cache. Options can be passed in the last argument.

Examples:


cache.write('a', 1)
cache.read_multi('a', 'b') # => { 'a' => 1, 'b' => nil }

Return all values for the given keys.



263
264
265
266
267
268
269
270
271
272
273
274
275
276
# File 'lib/readthis/cache.rb', line 263

def read_multi(*keys)
  options = merged_options(extract_options!(keys))
  mapping = keys.map { |key| namespaced_key(key, options) }

  return {} if keys.empty?

  invoke(:read_multi, keys) do |store|
    values = store.mget(*mapping).map { |value| entity.load(value) }

    refresh_entity(mapping, store, options)

    keys.zip(values).to_h
  end
end

#write(key, value, options = {}) ⇒ Object

Writes data to the cache using the given key. Will overwrite whatever value is already stored at that key.

Examples:


cache.write('some-key', 'a bunch of text')                     # => 'OK'
cache.write('some-key', 'short lived', expires_in: 60)         # => 'OK'
cache.write('some-key', 'lives elsehwere', namespace: 'cache') # => 'OK'


91
92
93
94
95
96
97
# File 'lib/readthis/cache.rb', line 91

def write(key, value, options = {})
  options = merged_options(options)

  invoke(:write, key) do |store|
    write_entity(key, value, store, options)
  end
end

#write_multi(hash, options = {}) ⇒ Object

Write multiple key value pairs simultaneously. This is an atomic operation that will always succeed and will overwrite existing values.

This is a non-standard, but useful, cache method.

Examples:


cache.write_multi({ 'a' => 1, 'b' => 2 }) # => true


291
292
293
294
295
296
297
298
299
# File 'lib/readthis/cache.rb', line 291

def write_multi(hash, options = {})
  options = merged_options(options)

  invoke(:write_multi, hash.keys) do |store|
    store.multi do
      hash.each { |key, value| write_entity(key, value, store, options) }
    end
  end
end