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.

Files changed (63) hide show
  1. data/CHANGELOG +57 -0
  2. data/lib/active_support/builder.rb +6 -0
  3. data/lib/active_support/cache.rb +428 -70
  4. data/lib/active_support/cache/compressed_mem_cache_store.rb +6 -15
  5. data/lib/active_support/cache/file_store.rb +139 -41
  6. data/lib/active_support/cache/mem_cache_store.rb +115 -76
  7. data/lib/active_support/cache/memory_store.rb +127 -27
  8. data/lib/active_support/cache/strategy/local_cache.rb +109 -57
  9. data/lib/active_support/cache/synchronized_memory_store.rb +2 -38
  10. data/lib/active_support/callbacks.rb +27 -27
  11. data/lib/active_support/configurable.rb +19 -18
  12. data/lib/active_support/core_ext/array/conversions.rb +30 -26
  13. data/lib/active_support/core_ext/array/random_access.rb +19 -5
  14. data/lib/active_support/core_ext/benchmark.rb +0 -12
  15. data/lib/active_support/core_ext/class/attribute.rb +1 -4
  16. data/lib/active_support/core_ext/class/inheritable_attributes.rb +3 -0
  17. data/lib/active_support/core_ext/date/calculations.rb +27 -8
  18. data/lib/active_support/core_ext/date/conversions.rb +1 -0
  19. data/lib/active_support/core_ext/date_time/conversions.rb +9 -3
  20. data/lib/active_support/core_ext/file.rb +1 -0
  21. data/lib/active_support/core_ext/hash/conversions.rb +14 -137
  22. data/lib/active_support/core_ext/kernel/debugger.rb +1 -1
  23. data/lib/active_support/core_ext/kernel/reporting.rb +2 -1
  24. data/lib/active_support/core_ext/load_error.rb +1 -0
  25. data/lib/active_support/core_ext/logger.rb +1 -1
  26. data/lib/active_support/core_ext/module/attr_internal.rb +2 -2
  27. data/lib/active_support/core_ext/object/to_param.rb +2 -2
  28. data/lib/active_support/core_ext/object/with_options.rb +2 -0
  29. data/lib/active_support/core_ext/string.rb +1 -0
  30. data/lib/active_support/core_ext/string/conversions.rb +35 -1
  31. data/lib/active_support/core_ext/string/encoding.rb +11 -0
  32. data/lib/active_support/core_ext/string/filters.rb +29 -0
  33. data/lib/active_support/core_ext/string/inflections.rb +0 -11
  34. data/lib/active_support/core_ext/string/interpolation.rb +1 -0
  35. data/lib/active_support/core_ext/string/multibyte.rb +16 -19
  36. data/lib/active_support/core_ext/time/calculations.rb +7 -6
  37. data/lib/active_support/core_ext/uri.rb +8 -3
  38. data/lib/active_support/dependencies.rb +33 -1
  39. data/lib/active_support/duration.rb +1 -0
  40. data/lib/active_support/hash_with_indifferent_access.rb +5 -1
  41. data/lib/active_support/i18n.rb +7 -2
  42. data/lib/active_support/inflector/transliterate.rb +58 -38
  43. data/lib/active_support/json/encoding.rb +28 -5
  44. data/lib/active_support/lazy_load_hooks.rb +14 -4
  45. data/lib/active_support/locale/en.yml +4 -1
  46. data/lib/active_support/message_verifier.rb +4 -4
  47. data/lib/active_support/multibyte.rb +1 -19
  48. data/lib/active_support/multibyte/chars.rb +143 -427
  49. data/lib/active_support/multibyte/unicode.rb +393 -0
  50. data/lib/active_support/notifications/fanout.rb +15 -5
  51. data/lib/active_support/notifications/instrumenter.rb +10 -4
  52. data/lib/active_support/railtie.rb +36 -0
  53. data/lib/active_support/rescuable.rb +1 -0
  54. data/lib/active_support/ruby/shim.rb +1 -0
  55. data/lib/active_support/testing/declarative.rb +1 -1
  56. data/lib/active_support/testing/isolation.rb +2 -1
  57. data/lib/active_support/testing/setup_and_teardown.rb +3 -0
  58. data/lib/active_support/values/time_zone.rb +20 -30
  59. data/lib/active_support/values/unicode_tables.dat +0 -0
  60. data/lib/active_support/version.rb +1 -1
  61. data/lib/active_support/xml_mini.rb +126 -1
  62. metadata +8 -61
  63. 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]
@@ -0,0 +1,6 @@
1
+ begin
2
+ require 'builder'
3
+ rescue LoadError => e
4
+ $stderr.puts "You don't have builder installed in your application. Please add it to your Gemfile and run bundle install"
5
+ raise e
6
+ end
@@ -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
- if EXPANDED_CACHE
70
- expanded_cache_key << "#{RAILS_CACHE_ID || RAILS_APP_VERION}/"
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
- # ActiveSupport::Cache::Store is meant for caching strings. Some cache
96
- # store implementations, like MemoryStore, are able to cache arbitrary
97
- # Ruby objects, but don't count on every cache store to be able to do that.
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
- cattr_accessor :logger, :instance_writter => false
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 #read, and calls #write on a cache miss.
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 +:expires_in+
160
- # option, which tells the memcached server to automatically expire the
161
- # cache item after a certain period. This options is also supported by
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, :expires_in => 5.seconds) do
166
- # "bar"
266
+ # cache.fetch("foo", :force => true, :raw => true) do
267
+ # :bar
167
268
  # end
168
269
  # cache.fetch("foo") # => "bar"
169
- # sleep(6)
170
- # cache.fetch("foo") # => nil
171
- def fetch(key, options = {}, &block)
172
- if !options[:force] && value = read(key, options)
173
- value
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, key, options, &block)
176
- write(key, result, options)
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
- # You may also specify additional options via the +options+ argument.
186
- # The specific cache store implementation will decide what to do with
187
- # +options+.
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
- # For example, FileStore supports the +:expires_in+ option, which
190
- # makes the method return nil for cache items older than the specified
191
- # period.
192
- def read(key, options = nil, &block)
193
- instrument(:read, key, options, &block)
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
- # For example, MemCacheStore supports the +:expires_in+ option, which
203
- # tells the memcached server to automatically expire the cache item after
204
- # a certain period:
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
- # cache = ActiveSupport::Cache::MemCacheStore.new
207
- # cache.write("foo", "bar", :expires_in => 5.seconds)
208
- # cache.read("foo") # => "bar"
209
- # sleep(6)
210
- # cache.read("foo") # => nil
211
- def write(key, value, options = nil, &block)
212
- instrument(:write, key, options, &block)
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
- def delete(key, options = nil, &block)
216
- instrument(:delete, key, options, &block)
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
- def delete_matched(matcher, options = nil, &block)
220
- instrument(:delete_matched, matcher.inspect, options, &block)
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
- def exist?(key, options = nil, &block)
224
- instrument(:exist?, key, options, &block)
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
- def increment(key, amount = 1)
228
- if num = read(key)
229
- write(key, num + amount)
230
- else
231
- nil
232
- end
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
- def decrement(key, amount = 1)
236
- if num = read(key)
237
- write(key, num - amount)
238
- else
239
- nil
240
- end
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
- def expires_in(options)
245
- expires_in = options && options[:expires_in]
246
- raise ":expires_in must be a number" if expires_in && !expires_in.is_a?(Numeric)
247
- expires_in || 0
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("active_support.cache_#{operation}", payload){ yield }
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