readthis 1.2.1 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 2c3b9c325211a544d3cdffbbb4356c7c392a5c04
4
- data.tar.gz: ecd6d0982a96bcff0f9bfafff60b6d758ab5ee55
3
+ metadata.gz: 3fc72dc492c9faadf1cdb788cffc7cc7f884b718
4
+ data.tar.gz: 9f3953c2e151c2533518c58deb434d0018cfa898
5
5
  SHA512:
6
- metadata.gz: 92323a5bfb41f8a29ea0b3082ff500078af9944d85fd756a1147b2cc1a7899e7fa4900c086463bfaa775608cf3d7c86137b4e54084f72ce1a2110b3f14d55c2b
7
- data.tar.gz: f2d19489b82236baee8be73033b07f3a375585195fc251b17c5ce9f15972911e696e038b282a949a513c8afda4465706f2a581b90c99529da7255e364fe9517b
6
+ metadata.gz: 4773d7f739dce07c3ee5f193f6966599d9dd12619bf8bfe1ad9d325563b1d00c70fcac0014e9b0d408167b929e6629401d44332bded3facc4b2581c3cb78ae7b
7
+ data.tar.gz: 709e8fdbfab3668d51e8a428223e8e976e9e49e60c1e3e10fa397dc6bf0d8445127466830472c203f8e3e663c42d789db474e2acecfc91d61f9d69148ef50f81
data/README.md CHANGED
@@ -90,6 +90,17 @@ Readthis::Cache.new(expires_in: 1.week) # don't do this
90
90
  Readthis::Cache.new(expires_in: 1.week.to_i) # do this
91
91
  ```
92
92
 
93
+ By using the `refresh` option the TTL for keys can be refreshed automatically
94
+ every time the key is read. This is helpful for ensuring commonly hit keys are
95
+ kept cached, effectively making the cache a hybrid LRU.
96
+
97
+ ```ruby
98
+ Readthis::Cache.new(refresh: true)
99
+ ```
100
+
101
+ Be aware that `refresh` adds a slight overhead to all read operations, as they
102
+ are now all write operations as well.
103
+
93
104
  ### Compression
94
105
 
95
106
  Compression can be enabled for all actions by passing the `compress` flag. By
data/lib/readthis.rb CHANGED
@@ -4,8 +4,6 @@ require 'readthis/serializers'
4
4
  require 'readthis/version'
5
5
 
6
6
  module Readthis
7
- extend self
8
-
9
7
  # The current, global, instance of serializers that is used by all cache
10
8
  # instances.
11
9
  #
@@ -39,4 +37,6 @@ module Readthis
39
37
  @fault_tolerant = nil
40
38
  @serializers = nil
41
39
  end
40
+
41
+ module_function :serializers, :fault_tolerant?, :fault_tolerant=, :reset!
42
42
  end
@@ -18,17 +18,19 @@ module Readthis
18
18
 
19
19
  # Creates a new Readthis::Cache object with the given options.
20
20
  #
21
- # @option [Hash] :redis Options that will be passed to the underlying redis connection
21
+ # @option [Hash] :redis Options that will be passed to the redis connection
22
22
  # @option [Boolean] :compress (false) Enable or disable automatic compression
23
- # @option [Number] :compression_threshold (8k) The size a string must be for compression
23
+ # @option [Number] :compression_threshold (8k) Minimum string size for compression
24
24
  # @option [Number] :expires_in The number of seconds until an entry expires
25
- # @option [Module] :marshal (Marshal) Any module that responds to `dump` and `load`
25
+ # @option [Boolean] :refresh (false) Automatically refresh key expiration
26
+ # @option [Module] :marshal (Marshal) Module that responds to `dump` and `load`
26
27
  # @option [String] :namespace Prefix used to namespace entries
27
28
  # @option [Number] :pool_size (5) The number of threads in the pool
28
29
  # @option [Number] :pool_timeout (5) How long before a thread times out
29
30
  #
30
31
  # @example Create a new cache instance
31
- # Readthis::Cache.new(namespace: 'cache', redis: { url: 'redis://localhost:6379/0' })
32
+ # Readthis::Cache.new(namespace: 'cache',
33
+ # redis: { url: 'redis://localhost:6379/0' })
32
34
  #
33
35
  # @example Create a compressed cache instance
34
36
  # Readthis::Cache.new(compress: true, compression_threshold: 2048)
@@ -60,10 +62,14 @@ module Readthis
60
62
  # cache.read('matched') # => 'some value'
61
63
  #
62
64
  def read(key, options = {})
65
+ options = merged_options(options)
66
+
63
67
  invoke(:read, key) do |store|
64
- value = store.get(namespaced_key(key, merged_options(options)))
68
+ key = namespaced_key(key, options)
69
+
70
+ refresh_entity(key, store, options)
65
71
 
66
- entity.load(value)
72
+ entity.load(store.get(key))
67
73
  end
68
74
  end
69
75
 
@@ -97,6 +103,7 @@ module Readthis
97
103
  #
98
104
  # cache.delete('existing-key') # => true
99
105
  # cache.delete('random-key') # => false
106
+ #
100
107
  def delete(key, options = {})
101
108
  namespaced = namespaced_key(key, merged_options(options))
102
109
 
@@ -130,7 +137,7 @@ module Readthis
130
137
  # cache.fetch('city') do
131
138
  # 'Duckburgh'
132
139
  # end
133
- # cache.fetch('city') # => "Duckburgh"
140
+ # cache.fetch('city') # => "Duckburgh"
134
141
  #
135
142
  # @example Cache Miss
136
143
  #
@@ -214,6 +221,8 @@ module Readthis
214
221
  invoke(:read_multi, keys) do |store|
215
222
  values = store.mget(*mapping).map { |value| entity.load(value) }
216
223
 
224
+ refresh_entity(mapping, store, options)
225
+
217
226
  keys.zip(values).to_h
218
227
  end
219
228
  end
@@ -310,12 +319,22 @@ module Readthis
310
319
 
311
320
  protected
312
321
 
322
+ def refresh_entity(keys, store, options)
323
+ return unless options[:refresh] && options[:expires_in]
324
+
325
+ store.multi do
326
+ Array(keys).each do |key|
327
+ store.expire(key, coerce_expiration(options[:expires_in]))
328
+ end
329
+ end
330
+ end
331
+
313
332
  def write_entity(key, value, store, options)
314
333
  namespaced = namespaced_key(key, options)
315
334
  dumped = entity.dump(value, options)
316
335
 
317
- if expiration = options[:expires_in]
318
- store.setex(namespaced, expiration.to_i, dumped)
336
+ if (expiration = options[:expires_in])
337
+ store.setex(namespaced, coerce_expiration(expiration), dumped)
319
338
  else
320
339
  store.set(namespaced, dumped)
321
340
  end
@@ -324,12 +343,15 @@ module Readthis
324
343
  private
325
344
 
326
345
  def alter(key, amount, options)
327
- number = read(key, options)
328
- delta = number.to_i + amount
346
+ delta = read(key, options).to_i + amount
329
347
  write(key, delta, options)
330
348
  delta
331
349
  end
332
350
 
351
+ def coerce_expiration(expires_in)
352
+ Float(expires_in).ceil
353
+ end
354
+
333
355
  def instrument(name, key)
334
356
  if self.class.notifications
335
357
  name = "cache_#{name}.active_support"
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Readthis
2
4
  module Passthrough
3
5
  def self.dump(value)
@@ -36,9 +36,9 @@ module Readthis
36
36
  def <<(serializer)
37
37
  case
38
38
  when serializers.frozen?
39
- fail SerializersFrozenError
39
+ raise SerializersFrozenError
40
40
  when serializers.length > SERIALIZER_LIMIT
41
- fail SerializersLimitError
41
+ raise SerializersLimitError
42
42
  else
43
43
  @serializers[serializer] = flags.max.succ
44
44
  @inverted = @serializers.invert
@@ -76,7 +76,7 @@ module Readthis
76
76
  flag = serializers[serializer]
77
77
 
78
78
  unless flag
79
- fail UnknownSerializerError, "'#{serializer}' hasn't been configured"
79
+ raise UnknownSerializerError, "'#{serializer}' hasn't been configured"
80
80
  end
81
81
 
82
82
  flag
@@ -1,3 +1,3 @@
1
1
  module Readthis
2
- VERSION = '1.2.1'
2
+ VERSION = '1.3.0'.freeze
3
3
  end
@@ -0,0 +1,13 @@
1
+ module RedisMatchers
2
+ extend RSpec::Matchers::DSL
3
+
4
+ matcher :have_ttl do |expected|
5
+ match do |cache|
6
+ cache.pool.with do |client|
7
+ expected.all? do |(key, value)|
8
+ client.ttl(key) == value
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -1,6 +1,9 @@
1
1
  require 'readthis'
2
+ require 'matchers/redis_matchers'
2
3
 
3
4
  RSpec.describe Readthis::Cache do
5
+ include RedisMatchers
6
+
4
7
  let(:cache) { Readthis::Cache.new }
5
8
 
6
9
  after do
@@ -48,16 +51,23 @@ RSpec.describe Readthis::Cache do
48
51
  end
49
52
 
50
53
  it 'uses a custom expiration' do
51
- cache = Readthis::Cache.new(namespace: 'cache', expires_in: 86_400)
54
+ cache = Readthis::Cache.new(expires_in: 10)
52
55
 
53
56
  cache.write('some-key', 'some-value')
54
57
  cache.write('other-key', 'other-value', expires_in: 1)
55
58
 
56
59
  expect(cache.read('some-key')).not_to be_nil
57
60
  expect(cache.read('other-key')).not_to be_nil
58
- sleep 1.01
59
- expect(cache.read('some-key')).not_to be_nil
60
- expect(cache.read('other-key')).to be_nil
61
+
62
+ expect(cache).to have_ttl('some-key' => 10, 'other-key' => 1)
63
+ end
64
+
65
+ it 'rounds floats to a valid expiration value' do
66
+ cache = Readthis::Cache.new
67
+
68
+ cache.write('some-key', 'some-value', expires_in: 0.1)
69
+
70
+ expect(cache).to have_ttl('some-key' => 1)
61
71
  end
62
72
 
63
73
  it 'expands non-string keys' do
@@ -73,6 +83,18 @@ RSpec.describe Readthis::Cache do
73
83
  it 'gracefully handles nil options' do
74
84
  expect { cache.read('whatever', nil) }.not_to raise_error
75
85
  end
86
+
87
+ it 'can refresh the expiration of an entity' do
88
+ cache = Readthis::Cache.new(refresh: true)
89
+
90
+ cache.write('some-key', 'some-value', expires_in: 1)
91
+
92
+ cache.read('some-key', expires_in: 2)
93
+ expect(cache).to have_ttl('some-key' => 2)
94
+
95
+ cache.read('some-key', expires_in: 0.1)
96
+ expect(cache).to have_ttl('some-key' => 1)
97
+ end
76
98
  end
77
99
 
78
100
  describe 'serializers' do
@@ -204,6 +226,17 @@ RSpec.describe Readthis::Cache do
204
226
  it 'returns {} with no keys' do
205
227
  expect(cache.read_multi(namespace: 'cache')).to eq({})
206
228
  end
229
+
230
+ it 'refreshes each key that is read' do
231
+ cache = Readthis::Cache.new(refresh: true)
232
+
233
+ cache.write('a', 1, expires_in: 1)
234
+ cache.write('b', 2, expires_in: 1)
235
+
236
+ cache.read_multi('a', 'b', expires_in: 2)
237
+
238
+ expect(cache).to have_ttl('a' => 2, 'b' => 2)
239
+ end
207
240
  end
208
241
 
209
242
  describe '#write_multi' do
@@ -224,8 +257,8 @@ RSpec.describe Readthis::Cache do
224
257
 
225
258
  expect(cache.read('a')).to be_nil
226
259
  expect(cache.read('a', namespace: 'multi')).to eq(1)
227
- sleep 1.01
228
- expect(cache.read('a', namespace: 'multi')).to be_nil
260
+
261
+ expect(cache).to have_ttl('multi:a' => 1)
229
262
  end
230
263
  end
231
264
 
@@ -328,10 +361,10 @@ RSpec.describe Readthis::Cache do
328
361
  cache.read('a')
329
362
 
330
363
  expect(events.length).to eq(2)
331
- expect(events.map(&:name)).to eq(%w[
364
+ expect(events.map(&:name)).to eq %w[
332
365
  cache_write.active_support
333
366
  cache_read.active_support
334
- ])
367
+ ]
335
368
  end
336
369
  end
337
370
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: readthis
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.1
4
+ version: 1.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Parker Selbert
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-04-06 00:00:00.000000000 Z
11
+ date: 2016-06-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: redis
@@ -125,6 +125,7 @@ files:
125
125
  - lib/readthis/passthrough.rb
126
126
  - lib/readthis/serializers.rb
127
127
  - lib/readthis/version.rb
128
+ - spec/matchers/redis_matchers.rb
128
129
  - spec/readthis/cache_spec.rb
129
130
  - spec/readthis/entity_spec.rb
130
131
  - spec/readthis/expanders_spec.rb
@@ -157,6 +158,7 @@ signing_key:
157
158
  specification_version: 4
158
159
  summary: Pooled active support compliant caching with redis
159
160
  test_files:
161
+ - spec/matchers/redis_matchers.rb
160
162
  - spec/readthis/cache_spec.rb
161
163
  - spec/readthis/entity_spec.rb
162
164
  - spec/readthis/expanders_spec.rb