readthis 0.8.1 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +31 -9
- data/lib/active_support/cache/readthis_store.rb +1 -1
- data/lib/readthis/cache.rb +24 -29
- data/lib/readthis/entity.rb +120 -28
- data/lib/readthis/expanders.rb +1 -1
- data/lib/readthis/serializers.rb +111 -0
- data/lib/readthis/version.rb +1 -1
- data/lib/readthis.rb +6 -0
- data/spec/readthis/cache_spec.rb +68 -20
- data/spec/readthis/entity_spec.rb +69 -12
- data/spec/readthis/expanders_spec.rb +1 -1
- data/spec/readthis/serializers_spec.rb +87 -0
- data/spec/readthis_spec.rb +9 -0
- data/spec/spec_helper.rb +1 -3
- metadata +23 -24
- data/.gitignore +0 -15
- data/.rspec +0 -2
- data/.travis.yml +0 -15
- data/CHANGELOG.md +0 -114
- data/CONTRIBUTING.md +0 -14
- data/Gemfile +0 -14
- data/LICENSE.txt +0 -22
- data/PERFORMANCE.md +0 -73
- data/Rakefile +0 -2
- data/benchmarks/compressed.rb +0 -74
- data/benchmarks/driver.rb +0 -18
- data/benchmarks/marshalling.rb +0 -40
- data/benchmarks/memory.rb +0 -11
- data/benchmarks/multi.rb +0 -64
- data/benchmarks/profile.rb +0 -20
- data/bin/rspec +0 -16
- data/lib/readthis/notifications.rb +0 -7
- data/readthis.gemspec +0 -27
- data/spec/readthis/notifications_spec.rb +0 -15
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 985ab4cb70b6ff45de0b279fe4d3ca90690cb206
|
4
|
+
data.tar.gz: d3c0519b74366c12278dc2a554d0a33c2914e7ac
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e0cec4bef654487ed5f99c46f27d05572993a823239d1993b6e0b534f0b699f170c773ef191cddf468e1b05936c588d23ac6716ddf7659c89a86710306f13c5b
|
7
|
+
data.tar.gz: 5f8ac666204946a58135953c5e4b23d64a12f412019d578b28c8b8b1a7c351c1004775c0470a812038b12d2577b87eec5df8a4e09f628e5fbb1bed3fc008517a
|
data/README.md
CHANGED
@@ -52,7 +52,11 @@ cache = Readthis::Cache.new(
|
|
52
52
|
)
|
53
53
|
```
|
54
54
|
|
55
|
+
You can also specify `host`, `port`, `db` or any other valid Redis options. For
|
56
|
+
more details about connection options see in [redis gem documentation][redisrb]
|
57
|
+
|
55
58
|
[store]: http://api.rubyonrails.org/classes/ActiveSupport/Cache/Store.html
|
59
|
+
[redisrb]: https://github.com/redis/redis-rb#getting-started
|
56
60
|
|
57
61
|
### Instances & Databases
|
58
62
|
|
@@ -96,25 +100,43 @@ config.cache_store = :readthis_store, {
|
|
96
100
|
}
|
97
101
|
```
|
98
102
|
|
99
|
-
###
|
103
|
+
### Serializing
|
100
104
|
|
101
|
-
Readthis uses Ruby's `Marshal` module for
|
102
|
-
|
103
|
-
|
105
|
+
Readthis uses Ruby's `Marshal` module for serializing all values by default.
|
106
|
+
This isn't always the fastest option, and depending on your use case it may be
|
107
|
+
desirable to use a faster but less flexible serializer.
|
104
108
|
|
105
|
-
|
109
|
+
By default Readthis knows about 3 different serializers:
|
106
110
|
|
107
|
-
|
108
|
-
|
109
|
-
|
111
|
+
* Marshal
|
112
|
+
* JSON
|
113
|
+
* Passthrough
|
110
114
|
|
111
115
|
If all cached data can safely be represented as a string then use the
|
112
|
-
pass-through
|
116
|
+
pass-through serializer:
|
113
117
|
|
114
118
|
```ruby
|
115
119
|
Readthis::Cache.new(marshal: Readthis::Passthrough)
|
116
120
|
```
|
117
121
|
|
122
|
+
You can introduce up to four additional serializers by configuring `serializers`
|
123
|
+
on the Readthis module. For example, if you wanted to use the extremely fast Oj
|
124
|
+
library for JSON serialization:
|
125
|
+
|
126
|
+
```ruby
|
127
|
+
Readthis.serializers << Oj
|
128
|
+
|
129
|
+
# Freeze the serializers to ensure they aren't changed at runtime.
|
130
|
+
Readthis.serializers.freeze!
|
131
|
+
|
132
|
+
Readthis::Cache.new(marshal: Oj)
|
133
|
+
```
|
134
|
+
|
135
|
+
Be aware that the order in which you add serializers matters. Serializers are
|
136
|
+
sticky and a flag is stored with each cached value. If you subsequently go to
|
137
|
+
deserialize values and haven't configured the same serializers in the same order
|
138
|
+
your application will raise errors.
|
139
|
+
|
118
140
|
## Differences From ActiveSupport::Cache
|
119
141
|
|
120
142
|
Readthis supports all of standard cache methods except for the following:
|
data/lib/readthis/cache.rb
CHANGED
@@ -1,24 +1,19 @@
|
|
1
1
|
require 'readthis/entity'
|
2
2
|
require 'readthis/expanders'
|
3
|
-
require 'readthis/notifications'
|
4
3
|
require 'readthis/passthrough'
|
5
4
|
require 'redis'
|
6
5
|
require 'connection_pool'
|
7
6
|
|
8
7
|
module Readthis
|
9
8
|
class Cache
|
10
|
-
attr_reader :entity, :
|
9
|
+
attr_reader :entity, :notifications, :options, :pool
|
11
10
|
|
12
11
|
# Provide a class level lookup of the proper notifications module.
|
13
12
|
# Instrumention is expected to occur within applications that have
|
14
13
|
# ActiveSupport::Notifications available, but needs to work even when it
|
15
14
|
# isn't.
|
16
15
|
def self.notifications
|
17
|
-
if defined?(ActiveSupport::Notifications)
|
18
|
-
ActiveSupport::Notifications
|
19
|
-
else
|
20
|
-
Readthis::Notifications
|
21
|
-
end
|
16
|
+
ActiveSupport::Notifications if defined?(ActiveSupport::Notifications)
|
22
17
|
end
|
23
18
|
|
24
19
|
# Creates a new Readthis::Cache object with the given options.
|
@@ -39,9 +34,7 @@ module Readthis
|
|
39
34
|
# Readthis::Cache.new(compress: true, compression_threshold: 2048)
|
40
35
|
#
|
41
36
|
def initialize(options = {})
|
42
|
-
@options
|
43
|
-
@expires_in = options.fetch(:expires_in, nil)
|
44
|
-
@namespace = options.fetch(:namespace, nil)
|
37
|
+
@options = options
|
45
38
|
|
46
39
|
@entity = Readthis::Entity.new(
|
47
40
|
marshal: options.fetch(:marshal, Marshal),
|
@@ -171,7 +164,7 @@ module Readthis
|
|
171
164
|
# cache.increment('counter', 2) # => 3
|
172
165
|
#
|
173
166
|
def increment(key, amount = 1, options = {})
|
174
|
-
invoke(:
|
167
|
+
invoke(:increment, key) do |_store|
|
175
168
|
alter(key, amount, options)
|
176
169
|
end
|
177
170
|
end
|
@@ -192,7 +185,7 @@ module Readthis
|
|
192
185
|
# cache.decrement('counter', 2) # => 17
|
193
186
|
#
|
194
187
|
def decrement(key, amount = 1, options = {})
|
195
|
-
invoke(:decrement, key) do |
|
188
|
+
invoke(:decrement, key) do |_store|
|
196
189
|
alter(key, amount * -1, options)
|
197
190
|
end
|
198
191
|
end
|
@@ -271,13 +264,13 @@ module Readthis
|
|
271
264
|
extracted = extract_options!(keys)
|
272
265
|
missing = {}
|
273
266
|
|
274
|
-
invoke(:fetch_multi, keys) do |
|
267
|
+
invoke(:fetch_multi, keys) do |_store|
|
275
268
|
results.each do |key, value|
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
269
|
+
next unless value.nil?
|
270
|
+
|
271
|
+
value = yield(key)
|
272
|
+
missing[key] = value
|
273
|
+
results[key] = value
|
281
274
|
end
|
282
275
|
end
|
283
276
|
|
@@ -310,7 +303,7 @@ module Readthis
|
|
310
303
|
# @example
|
311
304
|
#
|
312
305
|
# cache.clear #=> 'OK'
|
313
|
-
def clear(
|
306
|
+
def clear(_options = nil)
|
314
307
|
invoke(:clear, '*', &:flushdb)
|
315
308
|
end
|
316
309
|
|
@@ -318,11 +311,12 @@ module Readthis
|
|
318
311
|
|
319
312
|
def write_entity(key, value, store, options)
|
320
313
|
namespaced = namespaced_key(key, options)
|
314
|
+
dumped = entity.dump(value, options)
|
321
315
|
|
322
316
|
if expiration = options[:expires_in]
|
323
|
-
store.setex(namespaced, expiration.to_i,
|
317
|
+
store.setex(namespaced, expiration.to_i, dumped)
|
324
318
|
else
|
325
|
-
store.set(namespaced,
|
319
|
+
store.set(namespaced, dumped)
|
326
320
|
end
|
327
321
|
end
|
328
322
|
|
@@ -335,11 +329,15 @@ module Readthis
|
|
335
329
|
delta
|
336
330
|
end
|
337
331
|
|
338
|
-
def instrument(
|
339
|
-
|
340
|
-
|
332
|
+
def instrument(name, key)
|
333
|
+
if self.class.notifications
|
334
|
+
name = "cache_#{name}.active_support"
|
335
|
+
payload = { key: key, name: name }
|
341
336
|
|
342
|
-
|
337
|
+
self.class.notifications.instrument(name, payload) { yield(payload) }
|
338
|
+
else
|
339
|
+
yield
|
340
|
+
end
|
343
341
|
end
|
344
342
|
|
345
343
|
def invoke(operation, key, &block)
|
@@ -353,10 +351,7 @@ module Readthis
|
|
353
351
|
end
|
354
352
|
|
355
353
|
def merged_options(options)
|
356
|
-
options
|
357
|
-
options[:namespace] ||= namespace
|
358
|
-
options[:expires_in] ||= expires_in
|
359
|
-
options
|
354
|
+
(options || {}).merge!(@options)
|
360
355
|
end
|
361
356
|
|
362
357
|
def pool_options(options)
|
data/lib/readthis/entity.rb
CHANGED
@@ -2,51 +2,143 @@ require 'zlib'
|
|
2
2
|
|
3
3
|
module Readthis
|
4
4
|
class Entity
|
5
|
-
|
6
|
-
|
5
|
+
DEFAULT_OPTIONS = {
|
6
|
+
compress: false,
|
7
|
+
marshal: Marshal,
|
8
|
+
threshold: 8 * 1024
|
9
|
+
}.freeze
|
7
10
|
|
8
|
-
|
11
|
+
COMPRESSED_FLAG = 0x8
|
9
12
|
|
13
|
+
# Creates a Readthis::Entity with default options. Each option can be
|
14
|
+
# overridden later when entities are being dumped.
|
15
|
+
#
|
16
|
+
# Options are sticky, meaning that whatever is used when dumping will
|
17
|
+
# automatically be used again when loading, regardless of how current
|
18
|
+
# options are set.
|
19
|
+
#
|
20
|
+
# @option [Boolean] :compress (false) Enable or disable automatic compression
|
21
|
+
# @option [Module] :marshal (Marshal) Any module that responds to `dump` and `load`
|
22
|
+
# @option [Number] :threshold (8k) The size a string must be for compression
|
23
|
+
#
|
10
24
|
def initialize(options = {})
|
11
|
-
@
|
12
|
-
@compression = options.fetch(:compress, false)
|
13
|
-
@threshold = options.fetch(:threshold, DEFAULT_THRESHOLD)
|
25
|
+
@options = DEFAULT_OPTIONS.merge(options)
|
14
26
|
end
|
15
27
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
28
|
+
# Output a value prepared for cache storage. Passed options will override
|
29
|
+
# whatever has been specified for the instance.
|
30
|
+
#
|
31
|
+
# @param [String] String to dump
|
32
|
+
# @option [Boolean] :compress Enable or disable automatic compression
|
33
|
+
# @option [Module] :marshal Any module that responds to `dump` and `load`
|
34
|
+
# @option [Number] :threshold The size a string must be for compression
|
35
|
+
# @return [String] The prepared, possibly compressed, string
|
36
|
+
#
|
37
|
+
# @example Dumping a value using defaults
|
38
|
+
#
|
39
|
+
# entity.dump(string)
|
40
|
+
#
|
41
|
+
# @example Dumping a value with overrides
|
42
|
+
#
|
43
|
+
# entity.dump(string, compress: false)
|
44
|
+
#
|
45
|
+
def dump(value, options = {})
|
46
|
+
compress = with_fallback(options, :compress)
|
47
|
+
marshal = with_fallback(options, :marshal)
|
48
|
+
threshold = with_fallback(options, :threshold)
|
49
|
+
|
50
|
+
dumped = deflate(marshal.dump(value), compress, threshold)
|
51
|
+
|
52
|
+
compose(dumped, marshal, compress)
|
22
53
|
end
|
23
54
|
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
55
|
+
# Parse a dumped value using the embedded options.
|
56
|
+
#
|
57
|
+
# @param [String] Option embedded string to load
|
58
|
+
# @return [String] The original dumped string, restored
|
59
|
+
#
|
60
|
+
# @example
|
61
|
+
#
|
62
|
+
# entity.load(dumped)
|
63
|
+
#
|
64
|
+
def load(string)
|
65
|
+
marshal, compress, value = decompose(string)
|
66
|
+
|
67
|
+
marshal.load(inflate(value, compress))
|
68
|
+
rescue TypeError, NoMethodError
|
69
|
+
string
|
32
70
|
end
|
33
71
|
|
34
|
-
|
35
|
-
|
72
|
+
# Composes a single byte comprised of the chosen serializer and compression
|
73
|
+
# options. The byte is formatted as:
|
74
|
+
#
|
75
|
+
# | 0000 | 0 | 000 |
|
76
|
+
#
|
77
|
+
# Where there are four unused bits, 1 compression bit, and 3 bits for the
|
78
|
+
# serializer. This allows up to 8 different serializers for marshaling.
|
79
|
+
#
|
80
|
+
# @param [String] String to prefix with flags
|
81
|
+
# @param [Module] The marshal module to be used
|
82
|
+
# @param [Boolean] Flag determining whether the value is compressed
|
83
|
+
# @return [String] The original string with a single byte prefixed
|
84
|
+
#
|
85
|
+
# @example Compose an option embedded string
|
86
|
+
#
|
87
|
+
# entity.compose(string, Marshal, false) => 0x1 + string
|
88
|
+
# entity.compose(string, JSON, true) => 0x10 + string
|
89
|
+
#
|
90
|
+
def compose(value, marshal, compress)
|
91
|
+
flags = serializers.assoc(marshal)
|
92
|
+
flags |= COMPRESSED_FLAG if compress
|
93
|
+
|
94
|
+
value.prepend([flags].pack('C'))
|
36
95
|
end
|
37
96
|
|
38
|
-
|
39
|
-
|
97
|
+
# Decompose an option embedded string into marshal, compression and value.
|
98
|
+
#
|
99
|
+
# @param [String] Option embedded string to
|
100
|
+
# @return [Array<Module, Boolean, String>] An array comprised of the
|
101
|
+
# marshal, compression flag, and the base string.
|
102
|
+
#
|
103
|
+
def decompose(string)
|
104
|
+
flags = string[0].unpack('C').first
|
105
|
+
|
106
|
+
if flags < 16
|
107
|
+
marshal = serializers.rassoc(flags)
|
108
|
+
compress = (flags & COMPRESSED_FLAG) != 0
|
109
|
+
|
110
|
+
[marshal, compress, string[1..-1]]
|
111
|
+
else
|
112
|
+
[@options[:marshal], @options[:compress], string]
|
113
|
+
end
|
40
114
|
end
|
41
115
|
|
42
116
|
private
|
43
117
|
|
44
|
-
def
|
45
|
-
|
118
|
+
def deflate(value, compress, threshold)
|
119
|
+
if compress && value.bytesize >= threshold
|
120
|
+
Zlib::Deflate.deflate(value)
|
121
|
+
else
|
122
|
+
value
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
def inflate(value, decompress)
|
127
|
+
if decompress
|
128
|
+
Zlib::Inflate.inflate(value)
|
129
|
+
else
|
130
|
+
value
|
131
|
+
end
|
132
|
+
rescue Zlib::Error
|
133
|
+
value
|
134
|
+
end
|
135
|
+
|
136
|
+
def serializers
|
137
|
+
Readthis.serializers
|
46
138
|
end
|
47
139
|
|
48
|
-
def
|
49
|
-
|
140
|
+
def with_fallback(options, key)
|
141
|
+
options.key?(key) ? options[key] : @options[key]
|
50
142
|
end
|
51
143
|
end
|
52
144
|
end
|
data/lib/readthis/expanders.rb
CHANGED
@@ -7,7 +7,7 @@ module Readthis
|
|
7
7
|
when key.is_a?(Array)
|
8
8
|
key.flat_map { |elem| expand_key(elem) }.join('/')
|
9
9
|
when key.is_a?(Hash)
|
10
|
-
key.sort_by { |
|
10
|
+
key.sort_by { |hkey, _| hkey.to_s }.map { |hkey, val| "#{hkey}=#{val}" }.join('/')
|
11
11
|
when key.respond_to?(:to_param)
|
12
12
|
key.to_param
|
13
13
|
else
|
@@ -0,0 +1,111 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'readthis/passthrough'
|
3
|
+
|
4
|
+
module Readthis
|
5
|
+
SerializersFrozenError = Class.new(StandardError)
|
6
|
+
SerializersLimitError = Class.new(StandardError)
|
7
|
+
UnknownSerializerError = Class.new(StandardError)
|
8
|
+
|
9
|
+
class Serializers
|
10
|
+
BASE_SERIALIZERS = {
|
11
|
+
Marshal => 0x1,
|
12
|
+
Passthrough => 0x2,
|
13
|
+
JSON => 0x3
|
14
|
+
}.freeze
|
15
|
+
|
16
|
+
SERIALIZER_LIMIT = 7
|
17
|
+
|
18
|
+
attr_reader :serializers, :inverted
|
19
|
+
|
20
|
+
# Creates a new Readthis::Serializers entity. No configuration is expected
|
21
|
+
# during initialization.
|
22
|
+
#
|
23
|
+
def initialize
|
24
|
+
reset!
|
25
|
+
end
|
26
|
+
|
27
|
+
# Append a new serializer. Up to 7 total serializers may be configured for
|
28
|
+
# any single application be configured for any single application. This
|
29
|
+
# limit is based on the number of bytes available in the option flag.
|
30
|
+
#
|
31
|
+
# @param [Module] Any object that responds to `dump` and `load`
|
32
|
+
# @return [self] Returns itself for possible chaining
|
33
|
+
#
|
34
|
+
# @example
|
35
|
+
#
|
36
|
+
# serializers = Readthis::Serializers.new
|
37
|
+
# serializers << Oj
|
38
|
+
#
|
39
|
+
def <<(serializer)
|
40
|
+
case
|
41
|
+
when serializers.frozen?
|
42
|
+
fail SerializersFrozenError
|
43
|
+
when serializers.length > SERIALIZER_LIMIT
|
44
|
+
fail SerializersLimitError
|
45
|
+
else
|
46
|
+
@serializers[serializer] = flags.max.succ
|
47
|
+
@inverted = @serializers.invert
|
48
|
+
end
|
49
|
+
|
50
|
+
self
|
51
|
+
end
|
52
|
+
|
53
|
+
# Freeze the serializers hash, preventing modification.
|
54
|
+
#
|
55
|
+
def freeze!
|
56
|
+
serializers.freeze
|
57
|
+
end
|
58
|
+
|
59
|
+
# Reset the instance back to the default state. Useful for cleanup during
|
60
|
+
# testing.
|
61
|
+
#
|
62
|
+
def reset!
|
63
|
+
@serializers = BASE_SERIALIZERS.dup
|
64
|
+
@inverted = @serializers.invert
|
65
|
+
end
|
66
|
+
|
67
|
+
# Find a flag for a serializer object.
|
68
|
+
#
|
69
|
+
# @param [Object] Look up a flag by object
|
70
|
+
# @return [Number] Corresponding flag for the serializer object
|
71
|
+
# @raise [UnknownSerializerError] Indicates that a serializer was
|
72
|
+
# specified, but hasn't been configured for usage.
|
73
|
+
#
|
74
|
+
# @example
|
75
|
+
#
|
76
|
+
# serializers.assoc(JSON) #=> 1
|
77
|
+
#
|
78
|
+
def assoc(serializer)
|
79
|
+
flag = serializers[serializer]
|
80
|
+
|
81
|
+
unless flag
|
82
|
+
fail UnknownSerializerError, "'#{serializer}' hasn't been configured"
|
83
|
+
end
|
84
|
+
|
85
|
+
flag
|
86
|
+
end
|
87
|
+
|
88
|
+
# Find a serializer object by flag value.
|
89
|
+
#
|
90
|
+
# @param [Number] Flag to look up the serializer object by
|
91
|
+
# @return [Module] The serializer object
|
92
|
+
#
|
93
|
+
# @example
|
94
|
+
#
|
95
|
+
# serializers.rassoc(1) #=> Marshal
|
96
|
+
#
|
97
|
+
def rassoc(flag)
|
98
|
+
inverted[flag & inverted.length]
|
99
|
+
end
|
100
|
+
|
101
|
+
# @private
|
102
|
+
def marshals
|
103
|
+
serializers.keys
|
104
|
+
end
|
105
|
+
|
106
|
+
# @private
|
107
|
+
def flags
|
108
|
+
serializers.values
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
data/lib/readthis/version.rb
CHANGED
data/lib/readthis.rb
CHANGED
data/spec/readthis/cache_spec.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
require 'readthis
|
1
|
+
require 'readthis'
|
2
2
|
|
3
3
|
RSpec.describe Readthis::Cache do
|
4
4
|
let(:cache) { Readthis::Cache.new }
|
@@ -8,18 +8,6 @@ RSpec.describe Readthis::Cache do
|
|
8
8
|
end
|
9
9
|
|
10
10
|
describe '#initialize' do
|
11
|
-
it 'accepts and persists a namespace' do
|
12
|
-
cache = Readthis::Cache.new(namespace: 'kash')
|
13
|
-
|
14
|
-
expect(cache.namespace).to eq('kash')
|
15
|
-
end
|
16
|
-
|
17
|
-
it 'accepts and persists an expiration' do
|
18
|
-
cache = Readthis::Cache.new(expires_in: 10)
|
19
|
-
|
20
|
-
expect(cache.expires_in).to eq(10)
|
21
|
-
end
|
22
|
-
|
23
11
|
it 'makes options available' do
|
24
12
|
cache = Readthis::Cache.new(namespace: 'cache', expires_in: 1)
|
25
13
|
|
@@ -82,19 +70,52 @@ RSpec.describe Readthis::Cache do
|
|
82
70
|
end
|
83
71
|
end
|
84
72
|
|
73
|
+
describe 'serializers' do
|
74
|
+
after do
|
75
|
+
Readthis.serializers.reset!
|
76
|
+
end
|
77
|
+
|
78
|
+
it 'uses globally configured serializers' do
|
79
|
+
custom = Class.new do
|
80
|
+
def self.dump(value)
|
81
|
+
value
|
82
|
+
end
|
83
|
+
|
84
|
+
def self.load(value)
|
85
|
+
value
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
Readthis.serializers << custom
|
90
|
+
|
91
|
+
cache.write('customized', 'some value', marshal: custom)
|
92
|
+
|
93
|
+
expect(cache.read('customized')).to eq('some value')
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
85
97
|
describe 'compression' do
|
86
|
-
it '
|
98
|
+
it 'roundtrips entries when compression is enabled' do
|
87
99
|
com_cache = Readthis::Cache.new(compress: true, compression_threshold: 8)
|
88
100
|
raw_cache = Readthis::Cache.new
|
89
101
|
value = 'enough text that it should be compressed'
|
90
102
|
|
91
103
|
com_cache.write('compressed', value)
|
92
104
|
|
93
|
-
expect(raw_cache.read('compressed')).not_to eq(value)
|
94
105
|
expect(com_cache.read('compressed')).to eq(value)
|
106
|
+
expect(raw_cache.read('compressed')).to eq(value)
|
107
|
+
end
|
108
|
+
|
109
|
+
it 'roundtrips entries with option overrides' do
|
110
|
+
cache = Readthis::Cache.new(compress: false)
|
111
|
+
value = 'enough text that it should be compressed'
|
112
|
+
|
113
|
+
cache.write('comp-round', value, marshal: JSON, compress: true, threshold: 8)
|
114
|
+
|
115
|
+
expect(cache.read('comp-round')).to eq(value)
|
95
116
|
end
|
96
117
|
|
97
|
-
it '
|
118
|
+
it 'roundtrips bulk entries when compression is enabled' do
|
98
119
|
cache = Readthis::Cache.new(compress: true, compression_threshold: 8)
|
99
120
|
value = 'also enough text to compress'
|
100
121
|
|
@@ -146,7 +167,7 @@ RSpec.describe Readthis::Cache do
|
|
146
167
|
expect(cache.read_multi('a', 'b', 'c')).to eq(
|
147
168
|
'a' => 1,
|
148
169
|
'b' => 2,
|
149
|
-
'c' => '3'
|
170
|
+
'c' => '3'
|
150
171
|
)
|
151
172
|
end
|
152
173
|
|
@@ -156,7 +177,7 @@ RSpec.describe Readthis::Cache do
|
|
156
177
|
|
157
178
|
expect(cache.read_multi('d', 'e', namespace: 'cache')).to eq(
|
158
179
|
'd' => 1,
|
159
|
-
'e' => 2
|
180
|
+
'e' => 2
|
160
181
|
)
|
161
182
|
end
|
162
183
|
|
@@ -175,7 +196,11 @@ RSpec.describe Readthis::Cache do
|
|
175
196
|
end
|
176
197
|
|
177
198
|
it 'respects passed options' do
|
178
|
-
cache.write_multi(
|
199
|
+
cache.write_multi(
|
200
|
+
{ 'a' => 1, 'b' => 2 },
|
201
|
+
namespace: 'multi',
|
202
|
+
expires_in: 1
|
203
|
+
)
|
179
204
|
|
180
205
|
expect(cache.read('a')).to be_nil
|
181
206
|
expect(cache.read('a', namespace: 'multi')).to eq(1)
|
@@ -194,7 +219,7 @@ RSpec.describe Readthis::Cache do
|
|
194
219
|
expect(results).to eq(
|
195
220
|
'a' => 1,
|
196
221
|
'b' => 'bb',
|
197
|
-
'c' => 3
|
222
|
+
'c' => 3
|
198
223
|
)
|
199
224
|
|
200
225
|
expect(cache.read('b')).to eq('bb')
|
@@ -266,4 +291,27 @@ RSpec.describe Readthis::Cache do
|
|
266
291
|
expect(cache.decrement('unknown')).to eq(-1)
|
267
292
|
end
|
268
293
|
end
|
294
|
+
|
295
|
+
describe 'instrumentation' do
|
296
|
+
it 'instruments cache invokations' do
|
297
|
+
require 'active_support/notifications'
|
298
|
+
|
299
|
+
notes = ActiveSupport::Notifications
|
300
|
+
cache = Readthis::Cache.new
|
301
|
+
events = []
|
302
|
+
|
303
|
+
notes.subscribe(/cache_*/) do |*args|
|
304
|
+
events << ActiveSupport::Notifications::Event.new(*args)
|
305
|
+
end
|
306
|
+
|
307
|
+
cache.write('a', 'a')
|
308
|
+
cache.read('a')
|
309
|
+
|
310
|
+
expect(events.length).to eq(2)
|
311
|
+
expect(events.map(&:name)).to eq(%w[
|
312
|
+
cache_write.active_support
|
313
|
+
cache_read.active_support
|
314
|
+
])
|
315
|
+
end
|
316
|
+
end
|
269
317
|
end
|