readthis 0.4.0 → 0.5.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: f5a03b37918d41dfd0e30151cb3049638df50aa8
4
- data.tar.gz: 9c80690714866bc6992eafa9c137e785cf72a88d
3
+ metadata.gz: d1763594017ac9b87a099ca476d01c9f3823a0b0
4
+ data.tar.gz: 23e9363a5cc0c997c70f923f0ea21c50477843ff
5
5
  SHA512:
6
- metadata.gz: 9297ce7d8525ecdc2715399b3b657fb3cc12e581e4e9d067d762f7e9a134ffe6a35e48f6867bf7a26902f5dc48b6106e5c6dd012bc5e16372ad1b715fa6faa60
7
- data.tar.gz: d431742cd9f908aabdd06dfeaab2ed99b7165158fff51c57c947d9857d336e2a5c82fe42edae15a41c7cdd8043b3fc950c09e6fd60feb4d69c571fb71e2fe6e3
6
+ metadata.gz: 0c3868ec4afab92154c4a9bc542de628224086760a6c8c5275d9ba83ede290b4800f209eb3c3b5349d11cd2f2f63e0aa5858e587308ac9d2988ecc6a35989511
7
+ data.tar.gz: 3ffd2cc9546da805453a27d5ca33a4a35525582eb7905973a45293e4093ed47c70ebe37b4e3fb8f16035074cb558de08e271b2de712b8f52e237a8f386db9ca3
data/CHANGELOG.md CHANGED
@@ -1,3 +1,15 @@
1
+ ## v0.5.0 2014-12-12
2
+
3
+ - Added: All read and write operations are marshalled to and from storage. This
4
+ allows hashes, arrays, etc. to be restored instead of always returning a
5
+ string. Unlike `ActiveSupport::Store::Entity`, no new objects are allocated
6
+ for each entity, reducing GC and improving performance.
7
+ - Fixed: Increment/Decrement interface was only accepting two params instead of
8
+ three. Now accepts `amount` as the second parameter.
9
+ - Changed: Increment/Decrement no longer use `incby` and `decby`, as they don't
10
+ work with marshalled values. This means they are not entirely atomic, so race
11
+ conditions are possible.
12
+
1
13
  ## v0.4.0 2014-12-11
2
14
 
3
15
  - Added: Force the use of `hiredis` as the adapter. It is dramatically faster,
data/Gemfile CHANGED
@@ -2,8 +2,11 @@ source 'https://rubygems.org'
2
2
 
3
3
  gemspec
4
4
 
5
+ gem 'coveralls', require: false
6
+
5
7
  group :benchmarking do
6
8
  gem 'benchmark-ips'
7
9
  gem 'dalli'
10
+ gem 'oj'
8
11
  gem 'redis-activesupport', github: 'sorentwo/redis-activesupport'
9
12
  end
data/PERFORMANCE.md CHANGED
@@ -19,55 +19,55 @@ Performance compared to `dalli` and `redis-activesupport`:
19
19
  ```
20
20
  Raw Read Multi:
21
21
  Calculating -------------------------------------
22
- readthis:read-multi 500.000 i/100ms
23
- redisas:read-multi 95.000 i/100ms
24
- dalli:read-multi 97.000 i/100ms
22
+ readthis:read-multi 358.000 i/100ms
23
+ redisas:read-multi 94.000 i/100ms
24
+ dalli:read-multi 99.000 i/100ms
25
25
  -------------------------------------------------
26
- readthis:read-multi 5.286k (± 2.7%) i/s - 26.500k
27
- redisas:read-multi 959.4054.2%) i/s - 4.845k
28
- dalli:read-multi 978.8032.1%) i/s - 4.947k
26
+ readthis:read-multi 3.800k (± 2.3%) i/s - 19.332k
27
+ redisas:read-multi 962.1993.6%) i/s - 4.888k
28
+ dalli:read-multi 995.3531.1%) i/s - 5.049k
29
29
 
30
30
  Comparison:
31
- readthis:read-multi: 5286.0 i/s
32
- dalli:read-multi: 978.8 i/s - 5.40x slower
33
- redisas:read-multi: 959.4 i/s - 5.51x slower
31
+ readthis:read-multi: 3799.8 i/s
32
+ dalli:read-multi: 995.4 i/s - 3.82x slower
33
+ redisas:read-multi: 962.2 i/s - 3.95x slower
34
34
 
35
35
  Raw Fetch Multi:
36
36
  Calculating -------------------------------------
37
- readthis:fetch-multi 448.000 i/100ms
38
- redisas:fetch-multi 84.000 i/100ms
39
- dalli:fetch-multi 99.000 i/100ms
37
+ readthis:fetch-multi 336.000 i/100ms
38
+ redisas:fetch-multi 86.000 i/100ms
39
+ dalli:fetch-multi 102.000 i/100ms
40
40
  -------------------------------------------------
41
- readthis:fetch-multi 4.682k (± 2.4%) i/s - 23.744k
42
- redisas:fetch-multi 848.1013.2%) i/s - 4.284k
43
- dalli:fetch-multi 1.006k (± 2.4%) i/s - 5.049k
41
+ readthis:fetch-multi 3.424k (± 2.6%) i/s - 17.136k
42
+ redisas:fetch-multi 874.8032.7%) i/s - 4.386k
43
+ dalli:fetch-multi 1.028k1.2%) i/s - 5.202k
44
44
 
45
45
  Comparison:
46
- readthis:fetch-multi: 4682.4 i/s
47
- dalli:fetch-multi: 1005.6 i/s - 4.66x slower
48
- redisas:fetch-multi: 848.1 i/s - 5.52x slower
46
+ readthis:fetch-multi: 3424.2 i/s
47
+ dalli:fetch-multi: 1027.7 i/s - 3.33x slower
48
+ redisas:fetch-multi: 874.8 i/s - 3.91x slower
49
49
 
50
50
  Compressed Writes:
51
51
  Calculating -------------------------------------
52
- readthis:write 1.003k i/100ms
53
- dalli:write 913.000 i/100ms
52
+ readthis:write 924.000 i/100ms
53
+ dalli:write 903.000 i/100ms
54
54
  -------------------------------------------------
55
- readthis:write 11.095k5.7%) i/s - 56.168k
56
- dalli:write 9.829k (± 1.8%) i/s - 49.302k
55
+ readthis:write 10.105k4.9%) i/s - 50.820k
56
+ dalli:write 9.662k (± 1.6%) i/s - 48.762k
57
57
 
58
58
  Comparison:
59
- readthis:write: 11095.5 i/s
60
- dalli:write: 9828.8 i/s - 1.13x slower
59
+ readthis:write: 10105.3 i/s
60
+ dalli:write: 9662.4 i/s - 1.05x slower
61
61
 
62
62
  Compressed Read Multi:
63
63
  Calculating -------------------------------------
64
- readthis:read_multi 446.000 i/100ms
65
- dalli:read_multi 97.000 i/100ms
64
+ readthis:read_multi 325.000 i/100ms
65
+ dalli:read_multi 100.000 i/100ms
66
66
  -------------------------------------------------
67
- readthis:read_multi 4.728k4.6%) i/s - 23.638k
68
- dalli:read_multi 985.986 (± 3.9%) i/s - 4.947k
67
+ readthis:read_multi 3.357k2.3%) i/s - 16.900k
68
+ dalli:read_multi 1.014k (± 3.1%) i/s - 5.100k
69
69
 
70
70
  Comparison:
71
- readthis:read_multi: 4728.3 i/s
72
- dalli:read_multi: 986.0 i/s - 4.80x slower
71
+ readthis:read_multi: 3356.5 i/s
72
+ dalli:read_multi: 1014.1 i/s - 3.31x slower
73
73
  ```
data/README.md CHANGED
@@ -1,6 +1,7 @@
1
1
  [![Gem Version](https://badge.fury.io/rb/readthis.svg)](http://badge.fury.io/rb/readthis)
2
2
  [![Build Status](https://travis-ci.org/sorentwo/readthis.svg?branch=master)](https://travis-ci.org/sorentwo/readthis)
3
3
  [![Code Climate](https://codeclimate.com/github/sorentwo/readthis/badges/gpa.svg)](https://codeclimate.com/github/sorentwo/readthis)
4
+ [![Coverage Status](https://img.shields.io/coveralls/sorentwo/readthis.svg)](https://coveralls.io/r/sorentwo/readthis?branch=master)
4
5
 
5
6
  # Readthis
6
7
 
@@ -13,7 +14,7 @@ behavior for `fetch_multi`.
13
14
 
14
15
  ## Footprint & Performance
15
16
 
16
- See [Performance][PERFORMANCE.md]
17
+ See [Performance](PERFORMANCE.md)
17
18
 
18
19
  ## Installation
19
20
 
@@ -51,6 +52,8 @@ redis database, which defaults to 0. For example, using database 2:
51
52
  REDIS_URL=redis://localhost:6379/2
52
53
  ```
53
54
 
55
+ ### Compression
56
+
54
57
  Compression can be enabled for all actions by passing the `compress` flag. By
55
58
  default all values greater than 1024k will be compressed automatically. If there
56
59
  is any content has not been stored with compression, or perhaps was compressed
@@ -65,6 +68,24 @@ config.cache_store = :readthis_store, ENV.fetch('REDIS_URL'), {
65
68
  }
66
69
  ```
67
70
 
71
+ ### Marshalling
72
+
73
+ Readthis uses Ruby's `Marshal` module for dumping and loading all values by
74
+ default. This isn't always the fastest option, depending on your use case it may
75
+ be desirable to use a faster but more flexible marshaller.
76
+
77
+ Use Oj for JSON marshalling, extremely fast, limited types:
78
+
79
+ ```ruby
80
+ Readthis::Cache.new(marshal: Oj)
81
+ ```
82
+
83
+ If you don't mind everything being a string then use the Passthrough marshal:
84
+
85
+ ```ruby
86
+ Readthis::Cache.new(marshal: Readthis::Passthrough)
87
+ ```
88
+
68
89
  ## Differences
69
90
 
70
91
  Readthis supports all of standard cache methods except for the following:
@@ -0,0 +1,37 @@
1
+ require 'bundler'
2
+
3
+ Bundler.setup
4
+
5
+ require 'benchmark/ips'
6
+ require 'json'
7
+ require 'oj'
8
+ require 'readthis'
9
+ require 'readthis/passthrough'
10
+
11
+ REDIS_URL = 'redis://localhost:6379/11'
12
+ OPTIONS = { compressed: false }
13
+
14
+ readthis_pass = Readthis::Cache.new(REDIS_URL, OPTIONS.merge(marshal: Readthis::Passthrough))
15
+ readthis_oj = Readthis::Cache.new(REDIS_URL, OPTIONS.merge(marshal: Oj))
16
+ readthis_json = Readthis::Cache.new(REDIS_URL, OPTIONS.merge(marshal: JSON))
17
+ readthis_ruby = Readthis::Cache.new(REDIS_URL, OPTIONS.merge(marshal: Marshal))
18
+
19
+ HASH = ('a'..'z').each_with_object({}) { |key, memo| memo[key] = key }
20
+
21
+ Benchmark.ips do |x|
22
+ x.report('pass:hash:dump') { readthis_oj.write('pass', HASH) }
23
+ x.report('oj:hash:dump') { readthis_oj.write('oj', HASH) }
24
+ x.report('json:hash:dump') { readthis_json.write('json', HASH) }
25
+ x.report('ruby:hash:dump') { readthis_ruby.write('ruby', HASH) }
26
+
27
+ x.compare!
28
+ end
29
+
30
+ Benchmark.ips do |x|
31
+ x.report('pass:hash:load') { readthis_oj.read('pass') }
32
+ x.report('oj:hash:load') { readthis_oj.read('oj') }
33
+ x.report('json:hash:load') { readthis_json.read('json') }
34
+ x.report('ruby:hash:load') { readthis_ruby.read('ruby') }
35
+
36
+ x.compare!
37
+ end
@@ -1,19 +1,14 @@
1
- require 'readthis/compressor'
1
+ require 'readthis/entity'
2
2
  require 'readthis/expanders'
3
3
  require 'readthis/notifications'
4
+ require 'readthis/passthrough'
4
5
  require 'redis'
5
6
  require 'hiredis'
6
7
  require 'connection_pool'
7
8
 
8
9
  module Readthis
9
10
  class Cache
10
- attr_reader :compress,
11
- :compression_threshold,
12
- :expires_in,
13
- :namespace,
14
- :pool
15
-
16
- alias_method :compress?, :compress
11
+ attr_reader :entity, :expires_in, :namespace, :pool
17
12
 
18
13
  # Provide a class level lookup of the proper notifications module.
19
14
  # Instrumention is expected to occur within applications that have
@@ -30,11 +25,14 @@ module Readthis
30
25
  # Creates a new Readthis::Cache object with the given redis URL. The URL
31
26
  # is parsed by the redis client directly.
32
27
  #
33
- # @param url [String] A redis compliant url with necessary connection details
34
- # @option options [String] :namespace Prefix used to namespace entries
35
- # @option options [Number] :expires_in The number of seconds until an entry expires
36
- # @option options [Boolean] :compress Enable or disable automatic compression
37
- # @option options [Number] :compression_threshold The size a string must be for compression
28
+ # @param [String] A redis compliant url with necessary connection details
29
+ # @option [Boolean] :compress (false) Enable or disable automatic compression
30
+ # @option [Number] :compression_threshold (8k) The size a string must be for compression
31
+ # @option [Number] :expires_in The number of seconds until an entry expires
32
+ # @option [Module] :marshal (Marshal) Any module that responds to `dump` and `load`
33
+ # @option [String] :namespace Prefix used to namespace entries
34
+ # @option [Number] :pool_size (5) The number of threads in the pool
35
+ # @option [Number] :pool_timeout (5) How long before a thread times out
38
36
  #
39
37
  # @example Create a new cache instance
40
38
  # Readthis::Cache.new('redis://localhost:6379/0', namespace: 'cache')
@@ -45,19 +43,35 @@ module Readthis
45
43
  def initialize(url, options = {})
46
44
  @expires_in = options.fetch(:expires_in, nil)
47
45
  @namespace = options.fetch(:namespace, nil)
48
- @compress = options.fetch(:compress, false)
49
- @compression_threshold = options.fetch(:compression_threshold, 1024)
46
+
47
+ @entity = Readthis::Entity.new(
48
+ marshal: options.fetch(:marshal, Marshal),
49
+ compress: options.fetch(:compress, false),
50
+ threshold: options.fetch(:compression_threshold, 1024)
51
+ )
50
52
 
51
53
  @pool = ConnectionPool.new(pool_options(options)) do
52
54
  Redis.new(url: url, driver: :hiredis)
53
55
  end
54
56
  end
55
57
 
58
+ # Fetches data from the cache, using the given key. If there is data in
59
+ # the cache with the given key, then that data is returned. Otherwise, nil
60
+ # is returned.
61
+ #
62
+ # @param [String] Key for lookup
63
+ # @param [Hash] Optional overrides
64
+ #
65
+ # @example
66
+ #
67
+ # cache.read('missing') # => nil
68
+ # cache.read('matched') # => 'some value'
69
+ #
56
70
  def read(key, options = {})
57
71
  invoke(:read, key) do |store|
58
72
  value = store.get(namespaced_key(key, merged_options(options)))
59
73
 
60
- decompressed(value)
74
+ entity.load(value)
61
75
  end
62
76
  end
63
77
 
@@ -67,9 +81,9 @@ module Readthis
67
81
 
68
82
  invoke(:write, key) do |store|
69
83
  if expiration = options[:expires_in]
70
- store.setex(namespaced, expiration, compressed(value))
84
+ store.setex(namespaced, expiration, entity.dump(value))
71
85
  else
72
- store.set(namespaced, compressed(value))
86
+ store.set(namespaced, entity.dump(value))
73
87
  end
74
88
  end
75
89
  end
@@ -91,15 +105,45 @@ module Readthis
91
105
  value
92
106
  end
93
107
 
94
- def increment(key, options = {})
108
+ # Increment a key in the store.
109
+ #
110
+ # If the key doesn't exist it will be initialized at 0. If the key exists
111
+ # but it isn't a Fixnum it will be initialized at 0.
112
+ #
113
+ # @param [String] Key for lookup
114
+ # @param [Fixnum] Value to increment by
115
+ # @param [Hash] Optional overrides
116
+ #
117
+ # @example
118
+ #
119
+ # cache.increment('counter') # => 0
120
+ # cache.increment('counter') # => 1
121
+ # cache.increment('counter', 2) # => 3
122
+ #
123
+ def increment(key, amount = 1, options = {})
95
124
  invoke(:incremenet, key) do |store|
96
- store.incr(namespaced_key(key, merged_options(options)))
125
+ alter(key, amount, options)
97
126
  end
98
127
  end
99
128
 
100
- def decrement(key, options = {})
129
+ # Decrement a key in the store.
130
+ #
131
+ # If the key doesn't exist it will be initialized at 0. If the key exists
132
+ # but it isn't a Fixnum it will be initialized at 0.
133
+ #
134
+ # @param [String] Key for lookup
135
+ # @param [Fixnum] Value to decrement by
136
+ # @param [Hash] Optional overrides
137
+ #
138
+ # @example
139
+ #
140
+ # cache.write('counter', 20) # => 20
141
+ # cache.decrement('counter') # => 19
142
+ # cache.decrement('counter', 2) # => 17
143
+ #
144
+ def decrement(key, amount = 1, options = {})
101
145
  invoke(:decrement, key) do |store|
102
- store.decr(namespaced_key(key, merged_options(options)))
146
+ alter(key, amount * -1, options)
103
147
  end
104
148
  end
105
149
 
@@ -108,7 +152,7 @@ module Readthis
108
152
  mapping = keys.map { |key| namespaced_key(key, options) }
109
153
 
110
154
  invoke(:read_multi, keys) do |store|
111
- values = decompressed_multi(store.mget(mapping))
155
+ values = store.mget(mapping).map { |value| entity.load(value) }
112
156
 
113
157
  keys.zip(values).to_h
114
158
  end
@@ -152,32 +196,11 @@ module Readthis
152
196
 
153
197
  private
154
198
 
155
- def compressed(value)
156
- if compress?
157
- compressor.compress(value)
158
- else
159
- value
160
- end
161
- end
162
-
163
- def decompressed(value)
164
- if compress?
165
- compressor.decompress(value)
166
- else
167
- value
168
- end
169
- end
170
-
171
- def decompressed_multi(values)
172
- if compress?
173
- values.map { |value| compressor.decompress(value) }
174
- else
175
- values
176
- end
177
- end
178
-
179
- def compressor
180
- @compressor ||= Readthis::Compressor.new(threshold: compression_threshold)
199
+ def alter(key, amount, options)
200
+ number = read(key, options)
201
+ delta = number.to_i + amount
202
+ write(key, delta, options)
203
+ delta
181
204
  end
182
205
 
183
206
  def instrument(operation, key)
@@ -0,0 +1,51 @@
1
+ require 'zlib'
2
+
3
+ module Readthis
4
+ class Entity
5
+ DEFAULT_THRESHOLD = 8 * 1024
6
+
7
+ attr_reader :marshal, :compression, :threshold
8
+
9
+ def initialize(marshal: Marshal, compress: false, threshold: DEFAULT_THRESHOLD)
10
+ @marshal = marshal
11
+ @compression = compress
12
+ @threshold = threshold
13
+ end
14
+
15
+ def dump(value)
16
+ return value if value.nil?
17
+
18
+ if compress?(value)
19
+ compress(value)
20
+ else
21
+ marshal.dump(value)
22
+ end
23
+ end
24
+
25
+ def load(value)
26
+ return value if value.nil?
27
+
28
+ if compress?(value)
29
+ decompress(value)
30
+ else
31
+ marshal.load(value)
32
+ end
33
+ rescue TypeError, Zlib::Error
34
+ value
35
+ end
36
+
37
+ def compress(value)
38
+ Zlib::Deflate.deflate(marshal.dump(value))
39
+ end
40
+
41
+ def decompress(value)
42
+ marshal.load(Zlib::Inflate.inflate(value))
43
+ end
44
+
45
+ private
46
+
47
+ def compress?(value)
48
+ compression && value.bytesize >= threshold
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,11 @@
1
+ module Readthis
2
+ module Passthrough
3
+ def self.dump(value)
4
+ value
5
+ end
6
+
7
+ def self.load(value)
8
+ value
9
+ end
10
+ end
11
+ end
@@ -1,3 +1,3 @@
1
1
  module Readthis
2
- VERSION = '0.4.0'
2
+ VERSION = '0.5.0'
3
3
  end
@@ -20,17 +20,6 @@ RSpec.describe Readthis::Cache do
20
20
 
21
21
  expect(cache.expires_in).to eq(10)
22
22
  end
23
-
24
- it 'stores compression parameters' do
25
- cache = Readthis::Cache.new(
26
- url,
27
- compress: true,
28
- compression_threshold: 8
29
- )
30
-
31
- expect(cache.compress).to be_truthy
32
- expect(cache.compression_threshold).to eq(8)
33
- end
34
23
  end
35
24
 
36
25
  describe '#write' do
@@ -47,6 +36,14 @@ RSpec.describe Readthis::Cache do
47
36
  expect(cache.read('some-key', namespace: 'cache')).to eq('some-value')
48
37
  end
49
38
 
39
+ it 'roundtrips values as their original type' do
40
+ object = { a: 1, b: 2 }
41
+
42
+ cache.write('obj-key', object)
43
+
44
+ expect(cache.read('obj-key')).to eq(object)
45
+ end
46
+
50
47
  it 'uses a custom expiration' do
51
48
  cache.write('some-key', 'some-value', expires_in: 1)
52
49
 
@@ -70,7 +67,7 @@ RSpec.describe Readthis::Cache do
70
67
  end
71
68
  end
72
69
 
73
- describe '#compress' do
70
+ describe 'compression' do
74
71
  it 'round trips entries when compression is enabled' do
75
72
  com_cache = Readthis::Cache.new(url, compress: true, compression_threshold: 8)
76
73
  raw_cache = Readthis::Cache.new(url)
@@ -129,11 +126,11 @@ RSpec.describe Readthis::Cache do
129
126
  it 'maps multiple values to keys' do
130
127
  cache.write('a', 1)
131
128
  cache.write('b', 2)
132
- cache.write('c', 3)
129
+ cache.write('c', '3')
133
130
 
134
131
  expect(cache.read_multi('a', 'b', 'c')).to eq(
135
- 'a' => '1',
136
- 'b' => '2',
132
+ 'a' => 1,
133
+ 'b' => 2,
137
134
  'c' => '3',
138
135
  )
139
136
  end
@@ -143,8 +140,8 @@ RSpec.describe Readthis::Cache do
143
140
  cache.write('e', 2, namespace: 'cache')
144
141
 
145
142
  expect(cache.read_multi('d', 'e', namespace: 'cache')).to eq(
146
- 'd' => '1',
147
- 'e' => '2',
143
+ 'd' => 1,
144
+ 'e' => 2,
148
145
  )
149
146
  end
150
147
  end
@@ -157,9 +154,9 @@ RSpec.describe Readthis::Cache do
157
154
  results = cache.fetch_multi('a', 'b', 'c') { |key| key + key }
158
155
 
159
156
  expect(results).to eq(
160
- 'a' => '1',
157
+ 'a' => 1,
161
158
  'b' => 'bb',
162
- 'c' => '3',
159
+ 'c' => 3,
163
160
  )
164
161
  end
165
162
  end
@@ -188,7 +185,7 @@ RSpec.describe Readthis::Cache do
188
185
  it 'atomically increases the stored integer' do
189
186
  cache.write('counter', 10)
190
187
  expect(cache.increment('counter')).to eq(11)
191
- expect(cache.read('counter')).to eq('11')
188
+ expect(cache.read('counter')).to eq(11)
192
189
  end
193
190
 
194
191
  it 'defaults a missing key to 1' do
@@ -0,0 +1,73 @@
1
+ require 'readthis/entity'
2
+ require 'json'
3
+
4
+ RSpec.describe Readthis::Entity do
5
+ describe '#dump' do
6
+ it 'marshals the object as a ruby string' do
7
+ string = 'some string'
8
+ entity = Readthis::Entity.new
9
+
10
+ expect(entity.dump(string)).to eq(Marshal.dump(string))
11
+ end
12
+
13
+ it 'marshals using a custom marshaller' do
14
+ string = 'some string'
15
+ entity = Readthis::Entity.new(marshal: JSON)
16
+
17
+ expect(entity.dump(string)).to eq(JSON.dump(string))
18
+ end
19
+
20
+ it 'applies compression when enabled' do
21
+ string = 'a very large string, huge I tell you'
22
+ entity = Readthis::Entity.new(compress: true, threshold: 8)
23
+ dumped = Marshal.dump(string)
24
+
25
+ expect(entity.dump(string)).not_to eq(dumped)
26
+ end
27
+
28
+ it 'does not dump nil values' do
29
+ entity = Readthis::Entity.new
30
+
31
+ expect(entity.dump(nil)).to be_nil
32
+ end
33
+ end
34
+
35
+ describe '#load' do
36
+ it 'unmarshals a value' do
37
+ object = { a: 1, b: '2' }
38
+ dumped = Marshal.dump(object)
39
+ entity = Readthis::Entity.new
40
+
41
+ expect(entity.load(dumped)).to eq(object)
42
+ end
43
+
44
+ it 'uncompresses when compression is enabled' do
45
+ string = 'another one of those huge strings'
46
+ entity = Readthis::Entity.new(compress: true, threshold: 0)
47
+ dumped = Marshal.dump(string)
48
+
49
+ compressed = entity.compress(dumped)
50
+
51
+ expect(entity.load(compressed)).not_to eq(string)
52
+ end
53
+
54
+ it 'does not try to load a nil value' do
55
+ entity = Readthis::Entity.new
56
+
57
+ expect(entity.load(nil)).to be_nil
58
+ end
59
+
60
+ it 'passes through the value when it fails to marshal' do
61
+ entity = Readthis::Entity.new
62
+
63
+ expect { entity.load('not marshalled') }.not_to raise_error
64
+ end
65
+
66
+ it 'passes through the value when it fails to decompress' do
67
+ entity = Readthis::Entity.new(compress: true, threshold: 0)
68
+ dumped = Marshal.dump('some sizable string')
69
+
70
+ expect { entity.load(dumped) }.not_to raise_error
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,17 @@
1
+ require 'readthis/passthrough'
2
+
3
+ RSpec.describe Readthis::Passthrough do
4
+ describe '.load' do
5
+ it 'passes through the provided value' do
6
+ value = Object.new
7
+ expect(Readthis::Passthrough.load(value)).to eq(value)
8
+ end
9
+ end
10
+
11
+ describe '.dump' do
12
+ it 'passes through the provided value' do
13
+ value = Object.new
14
+ expect(Readthis::Passthrough.dump(value)).to eq(value)
15
+ end
16
+ end
17
+ end
data/spec/spec_helper.rb CHANGED
@@ -1,3 +1,7 @@
1
+ require 'coveralls'
2
+
3
+ Coveralls.wear!
4
+
1
5
  RSpec.configure do |config|
2
6
  config.expect_with :rspec do |expectations|
3
7
  expectations.include_chain_clauses_in_custom_matcher_descriptions = true
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: 0.4.0
4
+ version: 0.5.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: 2014-12-11 00:00:00.000000000 Z
11
+ date: 2014-12-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: redis
@@ -112,21 +112,24 @@ files:
112
112
  - README.md
113
113
  - Rakefile
114
114
  - benchmarks/compressed.rb
115
+ - benchmarks/marshalling.rb
115
116
  - benchmarks/memory.rb
116
117
  - benchmarks/multi.rb
117
118
  - bin/rspec
118
119
  - lib/active_support/cache/readthis_store.rb
119
120
  - lib/readthis.rb
120
121
  - lib/readthis/cache.rb
121
- - lib/readthis/compressor.rb
122
+ - lib/readthis/entity.rb
122
123
  - lib/readthis/expanders.rb
123
124
  - lib/readthis/notifications.rb
125
+ - lib/readthis/passthrough.rb
124
126
  - lib/readthis/version.rb
125
127
  - readthis.gemspec
126
128
  - spec/readthis/cache_spec.rb
127
- - spec/readthis/compressor_spec.rb
129
+ - spec/readthis/entity_spec.rb
128
130
  - spec/readthis/expanders_spec.rb
129
131
  - spec/readthis/notifications_spec.rb
132
+ - spec/readthis/passthrough_spec.rb
130
133
  - spec/spec_helper.rb
131
134
  homepage: https://github.com/sorentwo/readthis
132
135
  licenses:
@@ -154,7 +157,8 @@ specification_version: 4
154
157
  summary: Pooled active support compliant caching with redis
155
158
  test_files:
156
159
  - spec/readthis/cache_spec.rb
157
- - spec/readthis/compressor_spec.rb
160
+ - spec/readthis/entity_spec.rb
158
161
  - spec/readthis/expanders_spec.rb
159
162
  - spec/readthis/notifications_spec.rb
163
+ - spec/readthis/passthrough_spec.rb
160
164
  - spec/spec_helper.rb
@@ -1,41 +0,0 @@
1
- require 'zlib'
2
-
3
- module Readthis
4
- class Compressor
5
- attr_reader :threshold
6
-
7
- # Create a new Readthis::Compressor object that pivots on the provided
8
- # threshold value.
9
- #
10
- # @param threshold [Number] the threshold size required for compression
11
- def initialize(threshold: 1024)
12
- @threshold = threshold
13
- end
14
-
15
- # Compress a value if its size is greater or equal to the current threshold.
16
- #
17
- # @param value [String] a string to compress
18
- def compress(value)
19
- if value.size >= threshold
20
- Zlib::Deflate.deflate(value)
21
- else
22
- value
23
- end
24
- end
25
-
26
- # Decompress a previously compressed object. It will attempt to decode a
27
- # value regardless of whether it has been compressed, but will rescue
28
- # decoding errors.
29
- #
30
- # @param value [String] a possibly compressed string to decompress
31
- def decompress(value)
32
- if value.size >= threshold
33
- Zlib::Inflate.inflate(value)
34
- else
35
- value
36
- end
37
- rescue Zlib::Error
38
- value
39
- end
40
- end
41
- end
@@ -1,38 +0,0 @@
1
- require 'readthis/compressor'
2
-
3
- RSpec.describe Readthis::Compressor do
4
- describe '#compress' do
5
- it 'compresses the input' do
6
- compressor = Readthis::Compressor.new(threshold: 0)
7
- input = 'aaa bbb ccc'
8
- output = compressor.compress(input)
9
-
10
- expect(input).not_to eq(output)
11
- end
12
-
13
- it 'passes input below the threshold size through uncompressed' do
14
- compressor = Readthis::Compressor.new(threshold: 1024)
15
- input = 'abcdefg'
16
-
17
- expect(compressor.compress(input)).to eq(input)
18
- end
19
- end
20
-
21
- describe '#decompress' do
22
- it 'decompresses compressed data' do
23
- compressor = Readthis::Compressor.new(threshold: 0)
24
- input = 'aaa bbb ccc'
25
- compressed = compressor.compress(input)
26
- decompressed = compressor.decompress(compressed)
27
-
28
- expect(decompressed).to eq(input)
29
- end
30
-
31
- it 'passes through decompression failures' do
32
- compressor = Readthis::Compressor.new(threshold: 0)
33
- input = 'abcdefg'
34
-
35
- expect(compressor.decompress(input)).to eq(input)
36
- end
37
- end
38
- end