readthis 1.2.1 → 1.3.0

Sign up to get free protection for your applications and to get access to all the features.
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