readthis 0.8.1 → 1.2.1

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: ea629e7d22f58baa04359d6fabaee9116caf4e5d
4
- data.tar.gz: ff5720742c337be8b178ca840064b3469766dcda
3
+ metadata.gz: 2c3b9c325211a544d3cdffbbb4356c7c392a5c04
4
+ data.tar.gz: ecd6d0982a96bcff0f9bfafff60b6d758ab5ee55
5
5
  SHA512:
6
- metadata.gz: b75d0eb316bb68975693ab64044cc3c30159b9511dde446b504e1075b5c6aa915bc3782290334001de3b921173714343c86110778bc41ec90f67dc84b9ef0704
7
- data.tar.gz: 765fae41b2f8d511cd99269e17ed31eacbb968239e97be78422a4b20793ae3325feefc2f19d411b6d0a17722a7b06bbba78230fe49e12c76e01edabb9fc93b43
6
+ metadata.gz: 92323a5bfb41f8a29ea0b3082ff500078af9944d85fd756a1147b2cc1a7899e7fa4900c086463bfaa775608cf3d7c86137b4e54084f72ce1a2110b3f14d55c2b
7
+ data.tar.gz: f2d19489b82236baee8be73033b07f3a375585195fc251b17c5ce9f15972911e696e038b282a949a513c8afda4465706f2a581b90c99529da7255e364fe9517b
data/README.md CHANGED
@@ -5,9 +5,10 @@
5
5
 
6
6
  # Readthis
7
7
 
8
- Readthis is a drop in replacement for any ActiveSupport compliant cache. It
9
- emphasizes performance and simplicity and takes some cues from Dalli the popular
10
- Memcache client.
8
+ Readthis is a Redis backed cache client for Ruby. It is a drop in replacement
9
+ for any `ActiveSupport` compliant cache and can also be used for [session
10
+ storage](#session-storage). Above all Readthis emphasizes performance,
11
+ simplicity, and explicitness.
11
12
 
12
13
  For new projects there isn't any reason to stick with Memcached. Redis is as
13
14
  fast, if not faster in many scenarios, and is far more likely to be used
@@ -52,7 +53,11 @@ cache = Readthis::Cache.new(
52
53
  )
53
54
  ```
54
55
 
56
+ You can also specify `host`, `port`, `db` or any other valid Redis options. For
57
+ more details about connection options see in [redis gem documentation][redisrb]
58
+
55
59
  [store]: http://api.rubyonrails.org/classes/ActiveSupport/Cache/Store.html
60
+ [redisrb]: https://github.com/redis/redis-rb#getting-started
56
61
 
57
62
  ### Instances & Databases
58
63
 
@@ -61,24 +66,29 @@ instances have numerous benefits like: more predictable performance, avoiding
61
66
  expires in favor of LRU, and tuning the persistence mechanism. See [Optimizing
62
67
  Redis Usage for Caching][optimizing-usage] for more details.
63
68
 
64
- [optimizing-usage]: http://sorentwo.com/2015/07/27/optimizing-redis-usage-for-caching.html
65
-
66
- At the very least you'll want to use a specific database for caching. In the
69
+ At the very least, you'll want to use a specific database for caching. In the
67
70
  event the database needs to be purged you can do so with a single `clear`
68
71
  command, rather than finding all keys in a namespace and deleting them.
69
72
  Appending a number between 0 and 15 will specify the redis database, which
70
- defaults to 0. For example, using database 2:
73
+ defaults to `0`. For example, using database `2`:
71
74
 
72
75
  ```bash
73
76
  REDIS_URL=redis://localhost:6379/2
74
77
  ```
75
78
 
79
+ [optimizing-usage]: http://sorentwo.com/2015/07/27/optimizing-redis-usage-for-caching.html
80
+
76
81
  ### Expiration
77
82
 
78
83
  Be sure to use an integer value when setting expiration time. The default
79
84
  representation of `ActiveSupport::Duration` values won't work when setting
80
85
  expiration time, which will cause all keys to have `-1` as the TTL. Expiration
81
- values are always cast as an integer on write.
86
+ values are always cast as an integer on write. For example:
87
+
88
+ ```ruby
89
+ Readthis::Cache.new(expires_in: 1.week) # don't do this
90
+ Readthis::Cache.new(expires_in: 1.week.to_i) # do this
91
+ ```
82
92
 
83
93
  ### Compression
84
94
 
@@ -96,23 +106,64 @@ config.cache_store = :readthis_store, {
96
106
  }
97
107
  ```
98
108
 
99
- ### Marshalling
109
+ ### Serializing
110
+
111
+ Readthis uses Ruby's `Marshal` module for serializing all values by default.
112
+ This isn't always the fastest option, and depending on your use case it may be
113
+ desirable to use a faster but less flexible serializer.
114
+
115
+ By default Readthis knows about 3 different serializers:
100
116
 
101
- Readthis uses Ruby's `Marshal` module for dumping and loading all values by
102
- default. This isn't always the fastest option, and depending on your use case it
103
- may be desirable to use a faster but less flexible marshaller.
117
+ * Marshal
118
+ * JSON
119
+ * Passthrough
104
120
 
105
- Use Oj for JSON marshalling, extremely fast, but supports limited types:
121
+ If all cached data can safely be represented as a string then use the
122
+ pass-through serializer:
123
+
124
+ ```ruby
125
+ Readthis::Cache.new(marshal: Readthis::Passthrough)
126
+ ```
127
+
128
+ You can introduce up to four additional serializers by configuring `serializers`
129
+ on the Readthis module. For example, if you wanted to use the extremely fast Oj
130
+ library for JSON serialization:
106
131
 
107
132
  ```ruby
133
+ Readthis.serializers << Oj
134
+
135
+ # Freeze the serializers to ensure they aren't changed at runtime.
136
+ Readthis.serializers.freeze!
137
+
108
138
  Readthis::Cache.new(marshal: Oj)
109
139
  ```
110
140
 
111
- If all cached data can safely be represented as a string then use the
112
- pass-through marshaller:
141
+ Be aware that the *order in which you add serializers matters*. Serializers are
142
+ sticky and a flag is stored with each cached value. If you subsequently go to
143
+ deserialize values and haven't configured the same serializers in the same order
144
+ your application will raise errors.
145
+
146
+ ## Fault Tolerance
147
+
148
+ In some situations it is desirable to keep serving requests from disk or the
149
+ database if Redis crashes. This can be achieved with connection fault tolerance
150
+ by enabling it at the top level:
113
151
 
114
152
  ```ruby
115
- Readthis::Cache.new(marshal: Readthis::Passthrough)
153
+ Readthis.fault_tolerant = true
154
+ ```
155
+
156
+ The default value is `false`, because while it may work for `fetch` operations,
157
+ it isn't compatible with other state-based commands like `increment`.
158
+
159
+ ## Running Arbitrary Redis Commands
160
+
161
+ Readthis provides access to the underlying Redis connection pool, allowing you
162
+ to run arbitrary commands directly through the cache instance. For example, if
163
+ you wanted to expire a key manually using an instance of `Rails.cache`:
164
+
165
+ ```ruby
166
+ Rails.cache.pool.with { |client| client.expire('foo-key', 60) }
116
167
  ```
117
168
 
118
169
  ## Differences From ActiveSupport::Cache
@@ -122,11 +173,34 @@ Readthis supports all of standard cache methods except for the following:
122
173
  * `cleanup` - Redis does this with TTL or LRU already.
123
174
  * `delete_matched` - You really don't want to perform key matching operations in
124
175
  Redis. They are linear time and only support basic globbing.
176
+ * `mute` and `silence!` - You can subscribe to the events `/cache*+active_support/` with `ActiveSupport::Notifications` to [log cache calls manually][notifications].
177
+
178
+ [notifications]: https://github.com/sorentwo/readthis/issues/22#issuecomment-142595938
125
179
 
126
180
  Like other `ActiveSupport::Cache` implementations it is possible to cache `nil`
127
181
  as a value. However, the fetch methods treat `nil` values as a cache miss and
128
182
  re-generate/re-cache the value. Caching `nil` isn't recommended.
129
183
 
184
+ ## Session Storage
185
+
186
+ By using [ActionDispatch::Session::CacheStore][cache-store] it's possible to
187
+ reuse `:readthis_store` or specify a new Readthis cache store for storing
188
+ sessions.
189
+
190
+ ```ruby
191
+ Rails.application.config.session_store :cache_store
192
+ ```
193
+
194
+ To specify a separate Readthis instance you can use the `:cache` option:
195
+
196
+ ```ruby
197
+ Rails.application.config.session_store :cache_store,
198
+ cache: Readthis::Cache.new,
199
+ expire_after: 2.weeks.to_i
200
+ ```
201
+
202
+ [cache-store]: http://api.rubyonrails.org/classes/ActionDispatch/Session/CacheStore.html
203
+
130
204
  ## Contributing
131
205
 
132
206
  1. Fork it
@@ -2,6 +2,6 @@ require 'readthis'
2
2
 
3
3
  module ActiveSupport
4
4
  module Cache
5
- ReadthisStore ||= Readthis::Cache
5
+ ReadthisStore ||= Readthis::Cache # rubocop:disable Style/ConstantName
6
6
  end
7
7
  end
@@ -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, :expires_in, :namespace, :options, :pool
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 = 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),
@@ -145,6 +138,7 @@ module Readthis
145
138
  # cache.fetch('today', force: true) # => nil
146
139
  #
147
140
  def fetch(key, options = {})
141
+ options ||= {}
148
142
  value = read(key, options) unless options[:force]
149
143
 
150
144
  if value.nil? && block_given?
@@ -171,7 +165,7 @@ module Readthis
171
165
  # cache.increment('counter', 2) # => 3
172
166
  #
173
167
  def increment(key, amount = 1, options = {})
174
- invoke(:incremenet, key) do |store|
168
+ invoke(:increment, key) do |_store|
175
169
  alter(key, amount, options)
176
170
  end
177
171
  end
@@ -192,7 +186,7 @@ module Readthis
192
186
  # cache.decrement('counter', 2) # => 17
193
187
  #
194
188
  def decrement(key, amount = 1, options = {})
195
- invoke(:decrement, key) do |store|
189
+ invoke(:decrement, key) do |_store|
196
190
  alter(key, amount * -1, options)
197
191
  end
198
192
  end
@@ -218,7 +212,7 @@ module Readthis
218
212
  return {} if keys.empty?
219
213
 
220
214
  invoke(:read_multi, keys) do |store|
221
- values = store.mget(mapping).map { |value| entity.load(value) }
215
+ values = store.mget(*mapping).map { |value| entity.load(value) }
222
216
 
223
217
  keys.zip(values).to_h
224
218
  end
@@ -271,13 +265,13 @@ module Readthis
271
265
  extracted = extract_options!(keys)
272
266
  missing = {}
273
267
 
274
- invoke(:fetch_multi, keys) do |store|
268
+ invoke(:fetch_multi, keys) do |_store|
275
269
  results.each do |key, value|
276
- if value.nil?
277
- value = yield(key)
278
- missing[key] = value
279
- results[key] = value
280
- end
270
+ next unless value.nil?
271
+
272
+ value = yield(key)
273
+ missing[key] = value
274
+ results[key] = value
281
275
  end
282
276
  end
283
277
 
@@ -310,7 +304,7 @@ module Readthis
310
304
  # @example
311
305
  #
312
306
  # cache.clear #=> 'OK'
313
- def clear(options = {})
307
+ def clear(_options = nil)
314
308
  invoke(:clear, '*', &:flushdb)
315
309
  end
316
310
 
@@ -318,11 +312,12 @@ module Readthis
318
312
 
319
313
  def write_entity(key, value, store, options)
320
314
  namespaced = namespaced_key(key, options)
315
+ dumped = entity.dump(value, options)
321
316
 
322
317
  if expiration = options[:expires_in]
323
- store.setex(namespaced, expiration.to_i, entity.dump(value))
318
+ store.setex(namespaced, expiration.to_i, dumped)
324
319
  else
325
- store.set(namespaced, entity.dump(value))
320
+ store.set(namespaced, dumped)
326
321
  end
327
322
  end
328
323
 
@@ -335,17 +330,23 @@ module Readthis
335
330
  delta
336
331
  end
337
332
 
338
- def instrument(operation, key)
339
- name = "cache_#{operation}.active_support"
340
- payload = { key: key }
333
+ def instrument(name, key)
334
+ if self.class.notifications
335
+ name = "cache_#{name}.active_support"
336
+ payload = { key: key, name: name }
341
337
 
342
- self.class.notifications.instrument(name, key) { yield(payload) }
338
+ self.class.notifications.instrument(name, payload) { yield(payload) }
339
+ else
340
+ yield
341
+ end
343
342
  end
344
343
 
345
344
  def invoke(operation, key, &block)
346
345
  instrument(operation, key) do
347
346
  pool.with(&block)
348
347
  end
348
+ rescue Redis::BaseError => error
349
+ raise error unless Readthis.fault_tolerant?
349
350
  end
350
351
 
351
352
  def extract_options!(array)
@@ -353,10 +354,7 @@ module Readthis
353
354
  end
354
355
 
355
356
  def merged_options(options)
356
- options = options || {}
357
- options[:namespace] ||= namespace
358
- options[:expires_in] ||= expires_in
359
- options
357
+ @options.merge(options || {})
360
358
  end
361
359
 
362
360
  def pool_options(options)
@@ -2,51 +2,143 @@ require 'zlib'
2
2
 
3
3
  module Readthis
4
4
  class Entity
5
- DEFAULT_THRESHOLD = 8 * 1024
6
- MAGIC_BYTES = [120, 156].freeze
5
+ DEFAULT_OPTIONS = {
6
+ compress: false,
7
+ marshal: Marshal,
8
+ threshold: 8 * 1024
9
+ }.freeze
7
10
 
8
- attr_reader :marshal, :compression, :threshold
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
- @marshal = options.fetch(:marshal, Marshal)
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
- def dump(value)
17
- if compress?(value)
18
- compress(value)
19
- else
20
- marshal.dump(value)
21
- end
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, marshal: JSON)
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
- def load(value)
25
- if compressed?(value)
26
- decompress(value)
27
- else
28
- marshal.load(value)
29
- end
30
- rescue TypeError, Zlib::Error
31
- value
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
- def compress(value)
35
- Zlib::Deflate.deflate(marshal.dump(value))
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
- def decompress(value)
39
- marshal.load(Zlib::Inflate.inflate(value))
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 compress?(value)
45
- compression && value.bytesize >= threshold
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 compressed?(value)
49
- compression && value[0, 2].unpack('CC') == MAGIC_BYTES
140
+ def with_fallback(options, key)
141
+ options.key?(key) ? options[key] : @options[key]
50
142
  end
51
143
  end
52
144
  end
@@ -0,0 +1,7 @@
1
+ module Readthis
2
+ ReadthisError = Class.new(StandardError)
3
+
4
+ SerializersFrozenError = Class.new(ReadthisError)
5
+ SerializersLimitError = Class.new(ReadthisError)
6
+ UnknownSerializerError = Class.new(ReadthisError)
7
+ end
@@ -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 { |key, _| key.to_s }.map { |key, val| "#{key}=#{val}" }.join('/')
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,108 @@
1
+ require 'json'
2
+ require 'readthis/errors'
3
+ require 'readthis/passthrough'
4
+
5
+ module Readthis
6
+ class Serializers
7
+ BASE_SERIALIZERS = {
8
+ Marshal => 0x1,
9
+ Passthrough => 0x2,
10
+ JSON => 0x3
11
+ }.freeze
12
+
13
+ SERIALIZER_LIMIT = 7
14
+
15
+ attr_reader :serializers, :inverted
16
+
17
+ # Creates a new Readthis::Serializers entity. No configuration is expected
18
+ # during initialization.
19
+ #
20
+ def initialize
21
+ reset!
22
+ end
23
+
24
+ # Append a new serializer. Up to 7 total serializers may be configured for
25
+ # any single application be configured for any single application. This
26
+ # limit is based on the number of bytes available in the option flag.
27
+ #
28
+ # @param [Module] Any object that responds to `dump` and `load`
29
+ # @return [self] Returns itself for possible chaining
30
+ #
31
+ # @example
32
+ #
33
+ # serializers = Readthis::Serializers.new
34
+ # serializers << Oj
35
+ #
36
+ def <<(serializer)
37
+ case
38
+ when serializers.frozen?
39
+ fail SerializersFrozenError
40
+ when serializers.length > SERIALIZER_LIMIT
41
+ fail SerializersLimitError
42
+ else
43
+ @serializers[serializer] = flags.max.succ
44
+ @inverted = @serializers.invert
45
+ end
46
+
47
+ self
48
+ end
49
+
50
+ # Freeze the serializers hash, preventing modification.
51
+ #
52
+ def freeze!
53
+ serializers.freeze
54
+ end
55
+
56
+ # Reset the instance back to the default state. Useful for cleanup during
57
+ # testing.
58
+ #
59
+ def reset!
60
+ @serializers = BASE_SERIALIZERS.dup
61
+ @inverted = @serializers.invert
62
+ end
63
+
64
+ # Find a flag for a serializer object.
65
+ #
66
+ # @param [Object] Look up a flag by object
67
+ # @return [Number] Corresponding flag for the serializer object
68
+ # @raise [UnknownSerializerError] Indicates that a serializer was
69
+ # specified, but hasn't been configured for usage.
70
+ #
71
+ # @example
72
+ #
73
+ # serializers.assoc(JSON) #=> 1
74
+ #
75
+ def assoc(serializer)
76
+ flag = serializers[serializer]
77
+
78
+ unless flag
79
+ fail UnknownSerializerError, "'#{serializer}' hasn't been configured"
80
+ end
81
+
82
+ flag
83
+ end
84
+
85
+ # Find a serializer object by flag value.
86
+ #
87
+ # @param [Number] Flag to look up the serializer object by
88
+ # @return [Module] The serializer object
89
+ #
90
+ # @example
91
+ #
92
+ # serializers.rassoc(1) #=> Marshal
93
+ #
94
+ def rassoc(flag)
95
+ inverted[flag & inverted.length]
96
+ end
97
+
98
+ # @private
99
+ def marshals
100
+ serializers.keys
101
+ end
102
+
103
+ # @private
104
+ def flags
105
+ serializers.values
106
+ end
107
+ end
108
+ end
@@ -1,3 +1,3 @@
1
1
  module Readthis
2
- VERSION = '0.8.1'
2
+ VERSION = '1.2.1'
3
3
  end
data/lib/readthis.rb CHANGED
@@ -1,5 +1,42 @@
1
1
  require 'readthis/cache'
2
+ require 'readthis/errors'
3
+ require 'readthis/serializers'
2
4
  require 'readthis/version'
3
5
 
4
6
  module Readthis
7
+ extend self
8
+
9
+ # The current, global, instance of serializers that is used by all cache
10
+ # instances.
11
+ #
12
+ # @returns [Readthis::Serializers] An cached Serializers instance
13
+ #
14
+ # @see readthis/serializers
15
+ #
16
+ def serializers
17
+ @serializers ||= Readthis::Serializers.new
18
+ end
19
+
20
+ # Indicates whether connection error tolerance is enabled. With tolerance
21
+ # enabled every operation will return a `nil` value.
22
+ #
23
+ # @returns [Boolean] True for enabled, false for disabled
24
+ #
25
+ def fault_tolerant?
26
+ @fault_tolerant
27
+ end
28
+
29
+ # Toggle fault tolerance for connection errors.
30
+ #
31
+ # @param [Boolean] The new value for fault tolerance
32
+ #
33
+ def fault_tolerant=(value)
34
+ @fault_tolerant = value
35
+ end
36
+
37
+ # @private
38
+ def reset!
39
+ @fault_tolerant = nil
40
+ @serializers = nil
41
+ end
5
42
  end