activesupport 3.0.0.beta3 → 3.0.0.beta4
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of activesupport might be problematic. Click here for more details.
- data/CHANGELOG +57 -0
- data/lib/active_support/builder.rb +6 -0
- data/lib/active_support/cache.rb +428 -70
- data/lib/active_support/cache/compressed_mem_cache_store.rb +6 -15
- data/lib/active_support/cache/file_store.rb +139 -41
- data/lib/active_support/cache/mem_cache_store.rb +115 -76
- data/lib/active_support/cache/memory_store.rb +127 -27
- data/lib/active_support/cache/strategy/local_cache.rb +109 -57
- data/lib/active_support/cache/synchronized_memory_store.rb +2 -38
- data/lib/active_support/callbacks.rb +27 -27
- data/lib/active_support/configurable.rb +19 -18
- data/lib/active_support/core_ext/array/conversions.rb +30 -26
- data/lib/active_support/core_ext/array/random_access.rb +19 -5
- data/lib/active_support/core_ext/benchmark.rb +0 -12
- data/lib/active_support/core_ext/class/attribute.rb +1 -4
- data/lib/active_support/core_ext/class/inheritable_attributes.rb +3 -0
- data/lib/active_support/core_ext/date/calculations.rb +27 -8
- data/lib/active_support/core_ext/date/conversions.rb +1 -0
- data/lib/active_support/core_ext/date_time/conversions.rb +9 -3
- data/lib/active_support/core_ext/file.rb +1 -0
- data/lib/active_support/core_ext/hash/conversions.rb +14 -137
- data/lib/active_support/core_ext/kernel/debugger.rb +1 -1
- data/lib/active_support/core_ext/kernel/reporting.rb +2 -1
- data/lib/active_support/core_ext/load_error.rb +1 -0
- data/lib/active_support/core_ext/logger.rb +1 -1
- data/lib/active_support/core_ext/module/attr_internal.rb +2 -2
- data/lib/active_support/core_ext/object/to_param.rb +2 -2
- data/lib/active_support/core_ext/object/with_options.rb +2 -0
- data/lib/active_support/core_ext/string.rb +1 -0
- data/lib/active_support/core_ext/string/conversions.rb +35 -1
- data/lib/active_support/core_ext/string/encoding.rb +11 -0
- data/lib/active_support/core_ext/string/filters.rb +29 -0
- data/lib/active_support/core_ext/string/inflections.rb +0 -11
- data/lib/active_support/core_ext/string/interpolation.rb +1 -0
- data/lib/active_support/core_ext/string/multibyte.rb +16 -19
- data/lib/active_support/core_ext/time/calculations.rb +7 -6
- data/lib/active_support/core_ext/uri.rb +8 -3
- data/lib/active_support/dependencies.rb +33 -1
- data/lib/active_support/duration.rb +1 -0
- data/lib/active_support/hash_with_indifferent_access.rb +5 -1
- data/lib/active_support/i18n.rb +7 -2
- data/lib/active_support/inflector/transliterate.rb +58 -38
- data/lib/active_support/json/encoding.rb +28 -5
- data/lib/active_support/lazy_load_hooks.rb +14 -4
- data/lib/active_support/locale/en.yml +4 -1
- data/lib/active_support/message_verifier.rb +4 -4
- data/lib/active_support/multibyte.rb +1 -19
- data/lib/active_support/multibyte/chars.rb +143 -427
- data/lib/active_support/multibyte/unicode.rb +393 -0
- data/lib/active_support/notifications/fanout.rb +15 -5
- data/lib/active_support/notifications/instrumenter.rb +10 -4
- data/lib/active_support/railtie.rb +36 -0
- data/lib/active_support/rescuable.rb +1 -0
- data/lib/active_support/ruby/shim.rb +1 -0
- data/lib/active_support/testing/declarative.rb +1 -1
- data/lib/active_support/testing/isolation.rb +2 -1
- data/lib/active_support/testing/setup_and_teardown.rb +3 -0
- data/lib/active_support/values/time_zone.rb +20 -30
- data/lib/active_support/values/unicode_tables.dat +0 -0
- data/lib/active_support/version.rb +1 -1
- data/lib/active_support/xml_mini.rb +126 -1
- metadata +8 -61
- data/lib/active_support/multibyte/unicode_database.rb +0 -71
data/CHANGELOG
CHANGED
@@ -1,5 +1,62 @@
|
|
1
|
+
*Rails 3.0.0 [beta 4] (June 8th, 2010)*
|
2
|
+
|
3
|
+
* Extracted String#truncate from TextHelper#truncate [DHH]
|
4
|
+
|
5
|
+
* Ruby 1.9: support UTF-8 case folding. #4595 [Norman Clarke]
|
6
|
+
|
7
|
+
* Removes Array#rand and backports Array#sample from Ruby 1.9, thanks to Marc-Andre Lafortune. [fxn]
|
8
|
+
|
9
|
+
* Ruby 1.9: Renames last_(month|year) to prev_(month|year) in Date and Time. [fxn]
|
10
|
+
|
11
|
+
* Aliases Date#sunday to Date#end_of_week. [fxn]
|
12
|
+
|
13
|
+
* Backports Date#>> from 1.9 so that calculations do the right thing around the calendar reform. [fxn]
|
14
|
+
|
15
|
+
* Date#to_time handles properly years in the range 0..138. [fxn]
|
16
|
+
|
17
|
+
* Deprecate {{}} as interpolation syntax for I18n in favor of %{} [José Valim]
|
18
|
+
|
19
|
+
* Array#to_xml is more powerful and able to handle the same types as Hash#to_xml #4490 [Neeraj Singh]
|
20
|
+
|
21
|
+
* Harmonize the caching API and refactor the backends. #4452 [Brian Durand]
|
22
|
+
All caches:
|
23
|
+
* Add default options to initializer that will be sent to all read, write, fetch, exist?, increment, and decrement
|
24
|
+
* Add support for the :expires_in option to fetch and write for all caches. Cache entries are stored with the create timestamp and a ttl so that expiration can be handled independently of the implementation.
|
25
|
+
* Add support for a :namespace option. This can be used to set a global prefix for cache entries.
|
26
|
+
* Deprecate expand_cache_key on ActiveSupport::Cache and move it to ActionController::Caching and ActionDispatch::Http::Cache since the logic in the method used some Rails specific environment variables and was only used by ActionPack classes. Not very DRY but there didn't seem to be a good shared spot and ActiveSupport really shouldn't be Rails specific.
|
27
|
+
* Add support for :race_condition_ttl to fetch. This setting can prevent race conditions on fetch calls where several processes try to regenerate a recently expired entry at once.
|
28
|
+
* Add support for :compress option to fetch and write which will compress any data over a configurable threshold.
|
29
|
+
* Nil values can now be stored in the cache and are distinct from cache misses for fetch.
|
30
|
+
* Easier API to create new implementations. Just need to implement the methods read_entry, write_entry, and delete_entry instead of overwriting existing methods.
|
31
|
+
* Since all cache implementations support storing objects, update the docs to state that ActiveCache::Cache::Store implementations should store objects. Keys, however, must be strings since some implementations require that.
|
32
|
+
* Increase test coverage.
|
33
|
+
* Document methods which are provided as convenience but which may not be universally available.
|
34
|
+
|
35
|
+
MemoryStore:
|
36
|
+
* MemoryStore can now safely be used as the cache for single server sites.
|
37
|
+
* Make thread safe so that the default cache implementation used by Rails is thread safe. The overhead is minimal and it is still the fastest store available.
|
38
|
+
* Provide :size initialization option indicating the maximum size of the cache in memory (defaults to 32Mb).
|
39
|
+
* Add prune logic that removes the least recently used cache entries to keep the cache size from exceeding the max.
|
40
|
+
* Deprecated SynchronizedMemoryStore since it isn't needed anymore.
|
41
|
+
|
42
|
+
FileStore:
|
43
|
+
* Escape key values so they will work as file names on all file systems, be consistent, and case sensitive
|
44
|
+
* Use a hash algorithm to segment the cache into sub directories so that a large cache doesn't exceed file system limits.
|
45
|
+
* FileStore can be slow so implement the LocalCache strategy to cache reads for the duration of a request.
|
46
|
+
* Add cleanup method to keep the disk from filling up with expired entries.
|
47
|
+
* Fix increment and decrement to use file system locks so they are consistent between processes.
|
48
|
+
|
49
|
+
MemCacheStore:
|
50
|
+
* Support all keys. Previously keys with spaces in them would fail
|
51
|
+
* Deprecate CompressedMemCacheStore since it isn't needed anymore (use :compress => true)
|
52
|
+
|
53
|
+
* JSON: encode objects that don't have a native JSON representation using to_hash, if available, instead of instance_values (the old fallback) or to_s (other encoders' default). Encode BigDecimal and Regexp encode as strings to conform with other encoders. Try to transcode non-UTF-8 strings. [Jeremy Kemper]
|
54
|
+
|
55
|
+
|
1
56
|
*Rails 3.0.0 [beta 3] (April 13th, 2010)*
|
2
57
|
|
58
|
+
* HashWithIndifferentAccess: remove inherited symbolize_keys! since its keys are always strings. [Santiago Pastorino]
|
59
|
+
|
3
60
|
* Improve transliteration quality. #4374 [Norman Clarke]
|
4
61
|
|
5
62
|
* Speed up and add Ruby 1.9 support for ActiveSupport::Multibyte::Chars#tidy_bytes. #4350 [Norman Clarke]
|
data/lib/active_support/cache.rb
CHANGED
@@ -1,8 +1,12 @@
|
|
1
1
|
require 'benchmark'
|
2
|
+
require 'zlib'
|
3
|
+
require 'active_support/core_ext/array/extract_options'
|
2
4
|
require 'active_support/core_ext/array/wrap'
|
3
5
|
require 'active_support/core_ext/benchmark'
|
4
6
|
require 'active_support/core_ext/exception'
|
5
7
|
require 'active_support/core_ext/class/attribute_accessors'
|
8
|
+
require 'active_support/core_ext/numeric/bytes'
|
9
|
+
require 'active_support/core_ext/numeric/time'
|
6
10
|
require 'active_support/core_ext/object/to_param'
|
7
11
|
require 'active_support/core_ext/string/inflections'
|
8
12
|
|
@@ -11,10 +15,16 @@ module ActiveSupport
|
|
11
15
|
module Cache
|
12
16
|
autoload :FileStore, 'active_support/cache/file_store'
|
13
17
|
autoload :MemoryStore, 'active_support/cache/memory_store'
|
14
|
-
autoload :SynchronizedMemoryStore, 'active_support/cache/synchronized_memory_store'
|
15
18
|
autoload :MemCacheStore, 'active_support/cache/mem_cache_store'
|
19
|
+
autoload :SynchronizedMemoryStore, 'active_support/cache/synchronized_memory_store'
|
16
20
|
autoload :CompressedMemCacheStore, 'active_support/cache/compressed_mem_cache_store'
|
17
21
|
|
22
|
+
EMPTY_OPTIONS = {}.freeze
|
23
|
+
|
24
|
+
# These options mean something to all cache implementations. Individual cache
|
25
|
+
# implementations may support additional optons.
|
26
|
+
UNIVERSAL_OPTIONS = [:namespace, :compress, :compress_threshold, :expires_in, :race_condition_ttl]
|
27
|
+
|
18
28
|
module Strategy
|
19
29
|
autoload :LocalCache, 'active_support/cache/strategy/local_cache'
|
20
30
|
end
|
@@ -59,15 +69,12 @@ module ActiveSupport
|
|
59
69
|
end
|
60
70
|
end
|
61
71
|
|
62
|
-
RAILS_CACHE_ID = ENV["RAILS_CACHE_ID"]
|
63
|
-
RAILS_APP_VERION = ENV["RAILS_APP_VERION"]
|
64
|
-
EXPANDED_CACHE = RAILS_CACHE_ID || RAILS_APP_VERION
|
65
|
-
|
66
72
|
def self.expand_cache_key(key, namespace = nil)
|
67
73
|
expanded_cache_key = namespace ? "#{namespace}/" : ""
|
68
74
|
|
69
|
-
|
70
|
-
|
75
|
+
prefix = ENV["RAILS_CACHE_ID"] || ENV["RAILS_APP_VERSION"]
|
76
|
+
if prefix
|
77
|
+
expanded_cache_key << "#{prefix}/"
|
71
78
|
end
|
72
79
|
|
73
80
|
expanded_cache_key <<
|
@@ -92,26 +99,75 @@ module ActiveSupport
|
|
92
99
|
# ActiveSupport::Cache::MemCacheStore. MemCacheStore is currently the most
|
93
100
|
# popular cache store for large production websites.
|
94
101
|
#
|
95
|
-
#
|
96
|
-
#
|
97
|
-
#
|
102
|
+
# Some implementations may not support all methods beyond the basic cache
|
103
|
+
# methods of +fetch+, +write+, +read+, +exist?+, and +delete+.
|
104
|
+
#
|
105
|
+
# ActiveSupport::Cache::Store can store any serializable Ruby object.
|
98
106
|
#
|
99
107
|
# cache = ActiveSupport::Cache::MemoryStore.new
|
100
108
|
#
|
101
109
|
# cache.read("city") # => nil
|
102
110
|
# cache.write("city", "Duckburgh")
|
103
111
|
# cache.read("city") # => "Duckburgh"
|
112
|
+
#
|
113
|
+
# Keys are always translated into Strings and are case sensitive. When an
|
114
|
+
# object is specified as a key, its +cache_key+ method will be called if it
|
115
|
+
# is defined. Otherwise, the +to_param+ method will be called. Hashes and
|
116
|
+
# Arrays can be used as keys. The elements will be delimited by slashes
|
117
|
+
# and Hashes elements will be sorted by key so they are consistent.
|
118
|
+
#
|
119
|
+
# cache.read("city") == cache.read(:city) # => true
|
120
|
+
#
|
121
|
+
# Nil values can be cached.
|
122
|
+
#
|
123
|
+
# If your cache is on a shared infrastructure, you can define a namespace for
|
124
|
+
# your cache entries. If a namespace is defined, it will be prefixed on to every
|
125
|
+
# key. The namespace can be either a static value or a Proc. If it is a Proc, it
|
126
|
+
# will be invoked when each key is evaluated so that you can use application logic
|
127
|
+
# to invalidate keys.
|
128
|
+
#
|
129
|
+
# cache.namespace = lambda { @last_mod_time } # Set the namespace to a variable
|
130
|
+
# @last_mod_time = Time.now # Invalidate the entire cache by changing namespace
|
131
|
+
#
|
132
|
+
# All caches support auto expiring content after a specified number of seconds.
|
133
|
+
# To set the cache entry time to live, you can either specify +:expires_in+ as
|
134
|
+
# an option to the constructor to have it affect all entries or to the +fetch+
|
135
|
+
# or +write+ methods for just one entry.
|
136
|
+
#
|
137
|
+
# cache = ActiveSupport::Cache::MemoryStore.new(:expire_in => 5.minutes)
|
138
|
+
# cache.write(key, value, :expire_in => 1.minute) # Set a lower value for one entry
|
139
|
+
#
|
140
|
+
# Caches can also store values in a compressed format to save space and reduce
|
141
|
+
# time spent sending data. Since there is some overhead, values must be large
|
142
|
+
# enough to warrant compression. To turn on compression either pass
|
143
|
+
# <tt>:compress => true</tt> in the initializer or to +fetch+ or +write+.
|
144
|
+
# To specify the threshold at which to compress values, set
|
145
|
+
# <tt>:compress_threshold</tt>. The default threshold is 32K.
|
104
146
|
class Store
|
105
|
-
|
147
|
+
|
148
|
+
cattr_accessor :logger, :instance_writer => true
|
106
149
|
|
107
150
|
attr_reader :silence
|
108
151
|
alias :silence? :silence
|
109
152
|
|
153
|
+
# Create a new cache. The options will be passed to any write method calls except
|
154
|
+
# for :namespace which can be used to set the global namespace for the cache.
|
155
|
+
def initialize (options = nil)
|
156
|
+
@options = options ? options.dup : {}
|
157
|
+
end
|
158
|
+
|
159
|
+
# Get the default options set when the cache was created.
|
160
|
+
def options
|
161
|
+
@options ||= {}
|
162
|
+
end
|
163
|
+
|
164
|
+
# Silence the logger.
|
110
165
|
def silence!
|
111
166
|
@silence = true
|
112
167
|
self
|
113
168
|
end
|
114
169
|
|
170
|
+
# Silence the logger within a block.
|
115
171
|
def mute
|
116
172
|
previous_silence, @silence = defined?(@silence) && @silence, true
|
117
173
|
yield
|
@@ -152,28 +208,85 @@ module ActiveSupport
|
|
152
208
|
# cache.write("today", "Monday")
|
153
209
|
# cache.fetch("today", :force => true) # => nil
|
154
210
|
#
|
211
|
+
# Setting <tt>:compress</tt> will store a large cache entry set by the call
|
212
|
+
# in a compressed format.
|
213
|
+
#
|
214
|
+
# Setting <tt>:expires_in</tt> will set an expiration time on the cache
|
215
|
+
# entry if it is set by call.
|
216
|
+
#
|
217
|
+
# Setting <tt>:race_condition_ttl</tt> will invoke logic on entries set with
|
218
|
+
# an <tt>:expires_in</tt> option. If an entry is found in the cache that is
|
219
|
+
# expired and it has been expired for less than the number of seconds specified
|
220
|
+
# by this option and a block was passed to the method call, then the expiration
|
221
|
+
# future time of the entry in the cache will be updated to that many seconds
|
222
|
+
# in the and the block will be evaluated and written to the cache.
|
223
|
+
#
|
224
|
+
# This is very useful in situations where a cache entry is used very frequently
|
225
|
+
# under heavy load. The first process to find an expired cache entry will then
|
226
|
+
# become responsible for regenerating that entry while other processes continue
|
227
|
+
# to use the slightly out of date entry. This can prevent race conditions where
|
228
|
+
# too many processes are trying to regenerate the entry all at once. If the
|
229
|
+
# process regenerating the entry errors out, the entry will be regenerated
|
230
|
+
# after the specified number of seconds.
|
231
|
+
#
|
232
|
+
# # Set all values to expire after one minute.
|
233
|
+
# cache = ActiveSupport::Cache::MemoryCache.new(:expires_in => 1.minute)
|
234
|
+
#
|
235
|
+
# cache.write("foo", "original value")
|
236
|
+
# val_1 = nil
|
237
|
+
# val_2 = nil
|
238
|
+
# sleep 60
|
239
|
+
#
|
240
|
+
# Thread.new do
|
241
|
+
# val_1 = cache.fetch("foo", :race_condition_ttl => 10) do
|
242
|
+
# sleep 1
|
243
|
+
# "new value 1"
|
244
|
+
# end
|
245
|
+
# end
|
246
|
+
#
|
247
|
+
# Thread.new do
|
248
|
+
# val_2 = cache.fetch("foo", :race_condition_ttl => 10) do
|
249
|
+
# "new value 2"
|
250
|
+
# end
|
251
|
+
# end
|
252
|
+
#
|
253
|
+
# # val_1 => "new value 1"
|
254
|
+
# # val_2 => "original value"
|
255
|
+
# # cache.fetch("foo") => "new value 1"
|
256
|
+
#
|
155
257
|
# Other options will be handled by the specific cache store implementation.
|
156
|
-
# Internally, #fetch calls #
|
258
|
+
# Internally, #fetch calls #read_entry, and calls #write_entry on a cache miss.
|
157
259
|
# +options+ will be passed to the #read and #write calls.
|
158
260
|
#
|
159
|
-
# For example, MemCacheStore's #write method supports the +:
|
160
|
-
# option, which tells the memcached server to
|
161
|
-
#
|
162
|
-
# FileStore's #read method. We can use this option with #fetch too:
|
261
|
+
# For example, MemCacheStore's #write method supports the +:raw+
|
262
|
+
# option, which tells the memcached server to store all values as strings.
|
263
|
+
# We can use this option with #fetch too:
|
163
264
|
#
|
164
265
|
# cache = ActiveSupport::Cache::MemCacheStore.new
|
165
|
-
# cache.fetch("foo", :force => true, :
|
166
|
-
#
|
266
|
+
# cache.fetch("foo", :force => true, :raw => true) do
|
267
|
+
# :bar
|
167
268
|
# end
|
168
269
|
# cache.fetch("foo") # => "bar"
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
270
|
+
def fetch(name, options = nil, &block)
|
271
|
+
options = merged_options(options)
|
272
|
+
key = namespaced_key(name, options)
|
273
|
+
entry = instrument(:read, name, options) { read_entry(key, options) } unless options[:force]
|
274
|
+
if entry && entry.expired?
|
275
|
+
race_ttl = options[:race_condition_ttl].to_f
|
276
|
+
if race_ttl and Time.now.to_f - entry.expires_at <= race_ttl
|
277
|
+
entry.expires_at = Time.now + race_ttl
|
278
|
+
write_entry(key, entry, :expires_in => race_ttl * 2)
|
279
|
+
else
|
280
|
+
delete_entry(key, options)
|
281
|
+
end
|
282
|
+
entry = nil
|
283
|
+
end
|
284
|
+
|
285
|
+
if entry
|
286
|
+
entry.value
|
174
287
|
elsif block_given?
|
175
|
-
result = instrument(:generate,
|
176
|
-
write(
|
288
|
+
result = instrument(:generate, name, options, &block)
|
289
|
+
write(name, result, options)
|
177
290
|
result
|
178
291
|
end
|
179
292
|
end
|
@@ -182,15 +295,47 @@ module ActiveSupport
|
|
182
295
|
# the cache with the given key, then that data is returned. Otherwise,
|
183
296
|
# nil is returned.
|
184
297
|
#
|
185
|
-
#
|
186
|
-
|
187
|
-
|
298
|
+
# Options are passed to the underlying cache implementation.
|
299
|
+
def read(name, options = nil)
|
300
|
+
options = merged_options(options)
|
301
|
+
key = namespaced_key(name, options)
|
302
|
+
instrument(:read, name, options) do
|
303
|
+
entry = read_entry(key, options)
|
304
|
+
if entry
|
305
|
+
if entry.expired?
|
306
|
+
delete_entry(key, options)
|
307
|
+
nil
|
308
|
+
else
|
309
|
+
entry.value
|
310
|
+
end
|
311
|
+
else
|
312
|
+
nil
|
313
|
+
end
|
314
|
+
end
|
315
|
+
end
|
316
|
+
|
317
|
+
# Read multiple values at once from the cache. Options can be passed
|
318
|
+
# in the last argument.
|
188
319
|
#
|
189
|
-
#
|
190
|
-
#
|
191
|
-
#
|
192
|
-
def
|
193
|
-
|
320
|
+
# Some cache implementation may optimize this method.
|
321
|
+
#
|
322
|
+
# Returns a hash mapping the names provided to the values found.
|
323
|
+
def read_multi(*names)
|
324
|
+
options = names.extract_options!
|
325
|
+
options = merged_options(options)
|
326
|
+
results = {}
|
327
|
+
names.each do |name|
|
328
|
+
key = namespaced_key(name, options)
|
329
|
+
entry = read_entry(key, options)
|
330
|
+
if entry
|
331
|
+
if entry.expired?
|
332
|
+
delete_entry(key)
|
333
|
+
else
|
334
|
+
results[name] = entry.value
|
335
|
+
end
|
336
|
+
end
|
337
|
+
end
|
338
|
+
results
|
194
339
|
end
|
195
340
|
|
196
341
|
# Writes the given value to the cache, with the given key.
|
@@ -198,70 +343,283 @@ module ActiveSupport
|
|
198
343
|
# You may also specify additional options via the +options+ argument.
|
199
344
|
# The specific cache store implementation will decide what to do with
|
200
345
|
# +options+.
|
346
|
+
def write(name, value, options = nil)
|
347
|
+
options = merged_options(options)
|
348
|
+
instrument(:write, name, options) do
|
349
|
+
entry = Entry.new(value, options)
|
350
|
+
write_entry(namespaced_key(name, options), entry, options)
|
351
|
+
end
|
352
|
+
end
|
353
|
+
|
354
|
+
# Delete an entry in the cache. Returns +true+ if there was an entry to delete.
|
201
355
|
#
|
202
|
-
#
|
203
|
-
|
204
|
-
|
356
|
+
# Options are passed to the underlying cache implementation.
|
357
|
+
def delete(name, options = nil)
|
358
|
+
options = merged_options(options)
|
359
|
+
instrument(:delete, name) do
|
360
|
+
delete_entry(namespaced_key(name, options), options)
|
361
|
+
end
|
362
|
+
end
|
363
|
+
|
364
|
+
# Return true if the cache contains an entry with this name.
|
205
365
|
#
|
206
|
-
#
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
366
|
+
# Options are passed to the underlying cache implementation.
|
367
|
+
def exist?(name, options = nil)
|
368
|
+
options = merged_options(options)
|
369
|
+
instrument(:exist?, name) do
|
370
|
+
entry = read_entry(namespaced_key(name, options), options)
|
371
|
+
if entry && !entry.expired?
|
372
|
+
true
|
373
|
+
else
|
374
|
+
false
|
375
|
+
end
|
376
|
+
end
|
213
377
|
end
|
214
378
|
|
215
|
-
|
216
|
-
|
379
|
+
# Delete all entries whose keys match a pattern.
|
380
|
+
#
|
381
|
+
# Options are passed to the underlying cache implementation.
|
382
|
+
#
|
383
|
+
# Not all implementations may support +delete_matched+.
|
384
|
+
def delete_matched(matcher, options = nil)
|
385
|
+
raise NotImplementedError.new("#{self.class.name} does not support delete_matched")
|
217
386
|
end
|
218
387
|
|
219
|
-
|
220
|
-
|
388
|
+
# Increment an integer value in the cache.
|
389
|
+
#
|
390
|
+
# Options are passed to the underlying cache implementation.
|
391
|
+
#
|
392
|
+
# Not all implementations may support +delete_matched+.
|
393
|
+
def increment(name, amount = 1, options = nil)
|
394
|
+
raise NotImplementedError.new("#{self.class.name} does not support increment")
|
221
395
|
end
|
222
396
|
|
223
|
-
|
224
|
-
|
397
|
+
# Increment an integer value in the cache.
|
398
|
+
#
|
399
|
+
# Options are passed to the underlying cache implementation.
|
400
|
+
#
|
401
|
+
# Not all implementations may support +delete_matched+.
|
402
|
+
def decrement(name, amount = 1, options = nil)
|
403
|
+
raise NotImplementedError.new("#{self.class.name} does not support decrement")
|
225
404
|
end
|
226
405
|
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
406
|
+
# Cleanup the cache by removing expired entries. Not all cache implementations may
|
407
|
+
# support this method.
|
408
|
+
#
|
409
|
+
# Options are passed to the underlying cache implementation.
|
410
|
+
#
|
411
|
+
# Not all implementations may support +delete_matched+.
|
412
|
+
def cleanup(options = nil)
|
413
|
+
raise NotImplementedError.new("#{self.class.name} does not support cleanup")
|
233
414
|
end
|
234
415
|
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
416
|
+
# Clear the entire cache. Not all cache implementations may support this method.
|
417
|
+
# You should be careful with this method since it could affect other processes
|
418
|
+
# if you are using a shared cache.
|
419
|
+
#
|
420
|
+
# Options are passed to the underlying cache implementation.
|
421
|
+
#
|
422
|
+
# Not all implementations may support +delete_matched+.
|
423
|
+
def clear(options = nil)
|
424
|
+
raise NotImplementedError.new("#{self.class.name} does not support clear")
|
241
425
|
end
|
242
426
|
|
427
|
+
protected
|
428
|
+
# Add the namespace defined in the options to a pattern designed to match keys.
|
429
|
+
# Implementations that support delete_matched should call this method to translate
|
430
|
+
# a pattern that matches names into one that matches namespaced keys.
|
431
|
+
def key_matcher(pattern, options)
|
432
|
+
prefix = options[:namespace].is_a?(Proc) ? options[:namespace].call : options[:namespace]
|
433
|
+
if prefix
|
434
|
+
source = pattern.source
|
435
|
+
if source.start_with?('^')
|
436
|
+
source = source[1, source.length]
|
437
|
+
else
|
438
|
+
source = ".*#{source[0, source.length]}"
|
439
|
+
end
|
440
|
+
Regexp.new("^#{Regexp.escape(prefix)}:#{source}", pattern.options)
|
441
|
+
else
|
442
|
+
pattern
|
443
|
+
end
|
444
|
+
end
|
445
|
+
|
446
|
+
# Read an entry from the cache implementation. Subclasses must implement this method.
|
447
|
+
def read_entry(key, options) # :nodoc:
|
448
|
+
raise NotImplementedError.new
|
449
|
+
end
|
450
|
+
|
451
|
+
# Write an entry to the cache implementation. Subclasses must implement this method.
|
452
|
+
def write_entry(key, entry, options) # :nodoc:
|
453
|
+
raise NotImplementedError.new
|
454
|
+
end
|
455
|
+
|
456
|
+
# Delete an entry from the cache implementation. Subclasses must implement this method.
|
457
|
+
def delete_entry(key, options) # :nodoc:
|
458
|
+
raise NotImplementedError.new
|
459
|
+
end
|
460
|
+
|
243
461
|
private
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
462
|
+
# Merge the default options with ones specific to a method call.
|
463
|
+
def merged_options(call_options) # :nodoc:
|
464
|
+
if call_options
|
465
|
+
options.merge(call_options)
|
466
|
+
else
|
467
|
+
options.dup
|
468
|
+
end
|
469
|
+
end
|
470
|
+
|
471
|
+
# Expand a key to be a consistent string value. If the object responds to +cache_key+,
|
472
|
+
# it will be called. Otherwise, the to_param method will be called. If the key is a
|
473
|
+
# Hash, the keys will be sorted alphabetically.
|
474
|
+
def expanded_key(key) # :nodoc:
|
475
|
+
if key.respond_to?(:cache_key)
|
476
|
+
key = key.cache_key.to_s
|
477
|
+
elsif key.is_a?(Array)
|
478
|
+
if key.size > 1
|
479
|
+
key.collect{|element| expanded_key(element)}.to_param
|
480
|
+
else
|
481
|
+
key.first.to_param
|
482
|
+
end
|
483
|
+
elsif key.is_a?(Hash)
|
484
|
+
key = key.to_a.sort{|a,b| a.first.to_s <=> b.first.to_s}.collect{|k,v| "#{k}=#{v}"}.to_param
|
485
|
+
else
|
486
|
+
key = key.to_param
|
487
|
+
end
|
488
|
+
end
|
489
|
+
|
490
|
+
# Prefix a key with the namespace. The two values will be delimited with a colon.
|
491
|
+
def namespaced_key(key, options)
|
492
|
+
key = expanded_key(key)
|
493
|
+
namespace = options[:namespace] if options
|
494
|
+
prefix = namespace.is_a?(Proc) ? namespace.call : namespace
|
495
|
+
key = "#{prefix}:#{key}" if prefix
|
496
|
+
key
|
248
497
|
end
|
249
498
|
|
250
|
-
def instrument(operation, key, options)
|
499
|
+
def instrument(operation, key, options = nil)
|
251
500
|
log(operation, key, options)
|
252
501
|
|
253
502
|
if self.class.instrument
|
254
503
|
payload = { :key => key }
|
255
504
|
payload.merge!(options) if options.is_a?(Hash)
|
256
|
-
ActiveSupport::Notifications.instrument("
|
505
|
+
ActiveSupport::Notifications.instrument("cache_#{operation}.active_support", payload){ yield }
|
257
506
|
else
|
258
507
|
yield
|
259
508
|
end
|
260
509
|
end
|
261
510
|
|
262
|
-
def log(operation, key, options)
|
263
|
-
return unless logger && !silence?
|
264
|
-
logger.debug("Cache #{operation}: #{key}#{options ? " (#{options.inspect})"
|
511
|
+
def log(operation, key, options = nil)
|
512
|
+
return unless logger && logger.debug? && !silence?
|
513
|
+
logger.debug("Cache #{operation}: #{key}#{options.blank? ? "" : " (#{options.inspect})"}")
|
514
|
+
end
|
515
|
+
end
|
516
|
+
|
517
|
+
# Entry that is put into caches. It supports expiration time on entries and can compress values
|
518
|
+
# to save space in the cache.
|
519
|
+
class Entry
|
520
|
+
attr_reader :created_at, :expires_in
|
521
|
+
|
522
|
+
DEFAULT_COMPRESS_LIMIT = 16.kilobytes
|
523
|
+
|
524
|
+
class << self
|
525
|
+
# Create an entry with internal attributes set. This method is intended to be
|
526
|
+
# used by implementations that store cache entries in a native format instead
|
527
|
+
# of as serialized Ruby objects.
|
528
|
+
def create (raw_value, created_at, options = {})
|
529
|
+
entry = new(nil)
|
530
|
+
entry.instance_variable_set(:@value, raw_value)
|
531
|
+
entry.instance_variable_set(:@created_at, created_at.to_f)
|
532
|
+
entry.instance_variable_set(:@compressed, !!options[:compressed])
|
533
|
+
entry.instance_variable_set(:@expires_in, options[:expires_in])
|
534
|
+
entry
|
535
|
+
end
|
536
|
+
end
|
537
|
+
|
538
|
+
# Create a new cache entry for the specified value. Options supported are
|
539
|
+
# +:compress+, +:compress_threshold+, and +:expires_in+.
|
540
|
+
def initialize(value, options = {})
|
541
|
+
@compressed = false
|
542
|
+
@expires_in = options[:expires_in]
|
543
|
+
@expires_in = @expires_in.to_f if @expires_in
|
544
|
+
@created_at = Time.now.to_f
|
545
|
+
if value
|
546
|
+
if should_compress?(value, options)
|
547
|
+
@value = Zlib::Deflate.deflate(Marshal.dump(value))
|
548
|
+
@compressed = true
|
549
|
+
else
|
550
|
+
@value = value
|
551
|
+
end
|
552
|
+
else
|
553
|
+
@value = nil
|
554
|
+
end
|
555
|
+
end
|
556
|
+
|
557
|
+
# Get the raw value. This value may be serialized and compressed.
|
558
|
+
def raw_value
|
559
|
+
@value
|
560
|
+
end
|
561
|
+
|
562
|
+
# Get the value stored in the cache.
|
563
|
+
def value
|
564
|
+
if @value
|
565
|
+
val = compressed? ? Marshal.load(Zlib::Inflate.inflate(@value)) : @value
|
566
|
+
unless val.frozen?
|
567
|
+
val.freeze rescue nil
|
568
|
+
end
|
569
|
+
val
|
570
|
+
end
|
571
|
+
end
|
572
|
+
|
573
|
+
def compressed?
|
574
|
+
@compressed
|
575
|
+
end
|
576
|
+
|
577
|
+
# Check if the entry is expired. The +expires_in+ parameter can override the
|
578
|
+
# value set when the entry was created.
|
579
|
+
def expired?
|
580
|
+
if @expires_in && @created_at + @expires_in <= Time.now.to_f
|
581
|
+
true
|
582
|
+
else
|
583
|
+
false
|
584
|
+
end
|
585
|
+
end
|
586
|
+
|
587
|
+
# Set a new time to live on the entry so it expires at the given time.
|
588
|
+
def expires_at=(time)
|
589
|
+
if time
|
590
|
+
@expires_in = time.to_f - @created_at
|
591
|
+
else
|
592
|
+
@expires_in = nil
|
593
|
+
end
|
594
|
+
end
|
595
|
+
|
596
|
+
# Seconds since the epoch when the cache entry will expire.
|
597
|
+
def expires_at
|
598
|
+
@expires_in ? @created_at + @expires_in : nil
|
599
|
+
end
|
600
|
+
|
601
|
+
# Get the size of the cached value. This could be less than value.size
|
602
|
+
# if the data is compressed.
|
603
|
+
def size
|
604
|
+
if @value.nil?
|
605
|
+
0
|
606
|
+
elsif @value.respond_to?(:bytesize)
|
607
|
+
@value.bytesize
|
608
|
+
else
|
609
|
+
Marshal.dump(@value).bytesize
|
610
|
+
end
|
611
|
+
end
|
612
|
+
|
613
|
+
private
|
614
|
+
def should_compress?(value, options)
|
615
|
+
if options[:compress] && value
|
616
|
+
unless value.is_a?(Numeric)
|
617
|
+
compress_threshold = options[:compress_threshold] || DEFAULT_COMPRESS_LIMIT
|
618
|
+
serialized_value = value.is_a?(String) ? value : Marshal.dump(value)
|
619
|
+
return true if serialized_value.size >= compress_threshold
|
620
|
+
end
|
621
|
+
end
|
622
|
+
false
|
265
623
|
end
|
266
624
|
end
|
267
625
|
end
|