readthis 0.8.1 → 1.0.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 +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
|