activesupport 3.2.22.5 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (214) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +364 -135
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +6 -4
  5. data/lib/active_support/backtrace_cleaner.rb +33 -25
  6. data/lib/active_support/basic_object.rb +7 -17
  7. data/lib/active_support/benchmarkable.rb +19 -15
  8. data/lib/active_support/buffered_logger.rb +9 -113
  9. data/lib/active_support/cache/file_store.rb +14 -14
  10. data/lib/active_support/cache/mem_cache_store.rb +24 -30
  11. data/lib/active_support/cache/memory_store.rb +2 -0
  12. data/lib/active_support/cache/strategy/local_cache.rb +48 -37
  13. data/lib/active_support/cache.rb +228 -183
  14. data/lib/active_support/callbacks.rb +201 -253
  15. data/lib/active_support/concern.rb +16 -23
  16. data/lib/active_support/concurrency/latch.rb +27 -0
  17. data/lib/active_support/configurable.rb +69 -12
  18. data/lib/active_support/core_ext/array/access.rb +17 -9
  19. data/lib/active_support/core_ext/array/conversions.rb +115 -55
  20. data/lib/active_support/core_ext/array/extract_options.rb +2 -2
  21. data/lib/active_support/core_ext/array/grouping.rb +21 -22
  22. data/lib/active_support/core_ext/array/uniq_by.rb +12 -9
  23. data/lib/active_support/core_ext/array/wrap.rb +13 -16
  24. data/lib/active_support/core_ext/array.rb +0 -1
  25. data/lib/active_support/core_ext/benchmark.rb +7 -0
  26. data/lib/active_support/core_ext/big_decimal/conversions.rb +8 -24
  27. data/lib/active_support/core_ext/class/attribute.rb +40 -32
  28. data/lib/active_support/core_ext/class/attribute_accessors.rb +14 -12
  29. data/lib/active_support/core_ext/class/delegating_attributes.rb +15 -19
  30. data/lib/active_support/core_ext/class/subclasses.rb +11 -5
  31. data/lib/active_support/core_ext/date/calculations.rb +44 -187
  32. data/lib/active_support/core_ext/date/conversions.rb +16 -38
  33. data/lib/active_support/core_ext/date/zones.rb +25 -2
  34. data/lib/active_support/core_ext/date.rb +5 -0
  35. data/lib/active_support/core_ext/date_and_time/calculations.rb +232 -0
  36. data/lib/active_support/core_ext/date_time/acts_like.rb +0 -1
  37. data/lib/active_support/core_ext/date_time/calculations.rb +77 -62
  38. data/lib/active_support/core_ext/date_time/conversions.rb +21 -33
  39. data/lib/active_support/core_ext/date_time/zones.rb +11 -8
  40. data/lib/active_support/core_ext/date_time.rb +4 -0
  41. data/lib/active_support/core_ext/enumerable.rb +26 -73
  42. data/lib/active_support/core_ext/file/atomic.rb +27 -11
  43. data/lib/active_support/core_ext/file.rb +0 -1
  44. data/lib/active_support/core_ext/hash/conversions.rb +145 -79
  45. data/lib/active_support/core_ext/hash/deep_merge.rb +14 -8
  46. data/lib/active_support/core_ext/hash/diff.rb +5 -4
  47. data/lib/active_support/core_ext/hash/except.rb +1 -9
  48. data/lib/active_support/core_ext/hash/indifferent_access.rb +4 -5
  49. data/lib/active_support/core_ext/hash/keys.rb +108 -24
  50. data/lib/active_support/core_ext/hash/reverse_merge.rb +2 -3
  51. data/lib/active_support/core_ext/hash/slice.rb +12 -12
  52. data/lib/active_support/core_ext/hash.rb +0 -1
  53. data/lib/active_support/core_ext/integer/inflections.rb +13 -1
  54. data/lib/active_support/core_ext/integer/time.rb +17 -12
  55. data/lib/active_support/core_ext/kernel/debugger.rb +2 -2
  56. data/lib/active_support/core_ext/kernel/reporting.rb +36 -22
  57. data/lib/active_support/core_ext/kernel/singleton_class.rb +0 -7
  58. data/lib/active_support/core_ext/load_error.rb +7 -5
  59. data/lib/active_support/core_ext/logger.rb +7 -23
  60. data/lib/active_support/core_ext/marshal.rb +21 -0
  61. data/lib/active_support/core_ext/module/aliasing.rb +8 -9
  62. data/lib/active_support/core_ext/module/anonymous.rb +2 -7
  63. data/lib/active_support/core_ext/module/attr_internal.rb +0 -1
  64. data/lib/active_support/core_ext/module/attribute_accessors.rb +12 -10
  65. data/lib/active_support/core_ext/module/delegation.rb +92 -49
  66. data/lib/active_support/core_ext/module/deprecation.rb +19 -3
  67. data/lib/active_support/core_ext/module/introspection.rb +17 -27
  68. data/lib/active_support/core_ext/module/qualified_const.rb +8 -20
  69. data/lib/active_support/core_ext/module/remove_method.rb +1 -5
  70. data/lib/active_support/core_ext/module.rb +1 -3
  71. data/lib/active_support/core_ext/numeric/conversions.rb +135 -0
  72. data/lib/active_support/core_ext/numeric/time.rb +6 -6
  73. data/lib/active_support/core_ext/numeric.rb +1 -0
  74. data/lib/active_support/core_ext/object/acts_like.rb +4 -4
  75. data/lib/active_support/core_ext/object/blank.rb +7 -23
  76. data/lib/active_support/core_ext/object/deep_dup.rb +46 -0
  77. data/lib/active_support/core_ext/object/duplicable.rb +1 -30
  78. data/lib/active_support/core_ext/object/inclusion.rb +8 -7
  79. data/lib/active_support/core_ext/object/instance_variables.rb +7 -12
  80. data/lib/active_support/core_ext/object/to_json.rb +8 -0
  81. data/lib/active_support/core_ext/object/to_param.rb +5 -2
  82. data/lib/active_support/core_ext/object/try.rb +46 -25
  83. data/lib/active_support/core_ext/object/with_options.rb +7 -8
  84. data/lib/active_support/core_ext/object.rb +1 -0
  85. data/lib/active_support/core_ext/proc.rb +3 -0
  86. data/lib/active_support/core_ext/range/conversions.rb +0 -2
  87. data/lib/active_support/core_ext/range/include_range.rb +3 -1
  88. data/lib/active_support/core_ext/range/overlaps.rb +1 -1
  89. data/lib/active_support/core_ext/range.rb +0 -2
  90. data/lib/active_support/core_ext/string/access.rb +95 -90
  91. data/lib/active_support/core_ext/string/conversions.rb +27 -38
  92. data/lib/active_support/core_ext/string/encoding.rb +6 -9
  93. data/lib/active_support/core_ext/string/filters.rb +24 -18
  94. data/lib/active_support/core_ext/string/indent.rb +43 -0
  95. data/lib/active_support/core_ext/string/inflections.rb +70 -60
  96. data/lib/active_support/core_ext/string/inquiry.rb +2 -2
  97. data/lib/active_support/core_ext/string/multibyte.rb +41 -64
  98. data/lib/active_support/core_ext/string/output_safety.rb +59 -51
  99. data/lib/active_support/core_ext/string/zones.rb +13 -0
  100. data/lib/active_support/core_ext/string.rb +2 -3
  101. data/lib/active_support/core_ext/struct.rb +6 -0
  102. data/lib/active_support/core_ext/thread.rb +74 -0
  103. data/lib/active_support/core_ext/time/calculations.rb +108 -184
  104. data/lib/active_support/core_ext/time/conversions.rb +27 -51
  105. data/lib/active_support/core_ext/time/marshal.rb +0 -27
  106. data/lib/active_support/core_ext/time/zones.rb +27 -17
  107. data/lib/active_support/core_ext/time.rb +5 -0
  108. data/lib/active_support/core_ext/uri.rb +13 -17
  109. data/lib/active_support/core_ext.rb +3 -2
  110. data/lib/active_support/dependencies/autoload.rb +47 -20
  111. data/lib/active_support/dependencies.rb +160 -141
  112. data/lib/active_support/deprecation/behaviors.rb +44 -30
  113. data/lib/active_support/deprecation/instance_delegator.rb +24 -0
  114. data/lib/active_support/deprecation/method_wrappers.rb +33 -18
  115. data/lib/active_support/deprecation/proxy_wrappers.rb +58 -13
  116. data/lib/active_support/deprecation/reporting.rb +40 -11
  117. data/lib/active_support/deprecation.rb +39 -14
  118. data/lib/active_support/descendants_tracker.rb +34 -19
  119. data/lib/active_support/duration.rb +6 -8
  120. data/lib/active_support/file_update_checker.rb +63 -47
  121. data/lib/active_support/gzip.rb +11 -5
  122. data/lib/active_support/hash_with_indifferent_access.rb +127 -42
  123. data/lib/active_support/i18n.rb +4 -0
  124. data/lib/active_support/i18n_railtie.rb +5 -22
  125. data/lib/active_support/inflections.rb +14 -12
  126. data/lib/active_support/inflector/inflections.rb +108 -71
  127. data/lib/active_support/inflector/methods.rb +181 -160
  128. data/lib/active_support/inflector/transliterate.rb +16 -17
  129. data/lib/active_support/json/decoding.rb +18 -17
  130. data/lib/active_support/json/encoding.rb +93 -39
  131. data/lib/active_support/json/variable.rb +10 -1
  132. data/lib/active_support/key_generator.rb +75 -0
  133. data/lib/active_support/lazy_load_hooks.rb +21 -19
  134. data/lib/active_support/locale/en.yml +100 -3
  135. data/lib/active_support/log_subscriber/test_helper.rb +18 -15
  136. data/lib/active_support/log_subscriber.rb +35 -52
  137. data/lib/active_support/logger.rb +57 -0
  138. data/lib/active_support/logger_silence.rb +24 -0
  139. data/lib/active_support/message_encryptor.rb +38 -35
  140. data/lib/active_support/message_verifier.rb +9 -15
  141. data/lib/active_support/multibyte/chars.rb +80 -333
  142. data/lib/active_support/multibyte/unicode.rb +74 -64
  143. data/lib/active_support/multibyte.rb +5 -28
  144. data/lib/active_support/notifications/fanout.rb +112 -18
  145. data/lib/active_support/notifications/instrumenter.rb +33 -14
  146. data/lib/active_support/notifications.rb +79 -26
  147. data/lib/active_support/number_helper.rb +637 -0
  148. data/lib/active_support/ordered_hash.rb +8 -190
  149. data/lib/active_support/ordered_options.rb +21 -23
  150. data/lib/active_support/per_thread_registry.rb +52 -0
  151. data/lib/active_support/proxy_object.rb +13 -0
  152. data/lib/active_support/rails.rb +27 -0
  153. data/lib/active_support/railtie.rb +12 -32
  154. data/lib/active_support/rescuable.rb +9 -4
  155. data/lib/active_support/string_inquirer.rb +13 -8
  156. data/lib/active_support/subscriber.rb +93 -0
  157. data/lib/active_support/tagged_logging.rb +51 -73
  158. data/lib/active_support/test_case.rb +46 -17
  159. data/lib/active_support/testing/assertions.rb +56 -26
  160. data/lib/active_support/testing/autorun.rb +5 -0
  161. data/lib/active_support/testing/constant_lookup.rb +54 -0
  162. data/lib/active_support/testing/declarative.rb +1 -1
  163. data/lib/active_support/testing/deprecation.rb +0 -19
  164. data/lib/active_support/testing/isolation.rb +29 -58
  165. data/lib/active_support/testing/pending.rb +5 -43
  166. data/lib/active_support/testing/setup_and_teardown.rb +6 -92
  167. data/lib/active_support/testing/tagged_logging.rb +25 -0
  168. data/lib/active_support/time.rb +6 -21
  169. data/lib/active_support/time_with_zone.rb +80 -43
  170. data/lib/active_support/values/time_zone.rb +81 -60
  171. data/lib/active_support/values/unicode_tables.dat +0 -0
  172. data/lib/active_support/version.rb +7 -6
  173. data/lib/active_support/xml_mini/jdom.rb +9 -11
  174. data/lib/active_support/xml_mini/libxml.rb +1 -2
  175. data/lib/active_support/xml_mini/libxmlsax.rb +2 -3
  176. data/lib/active_support/xml_mini/nokogiri.rb +1 -2
  177. data/lib/active_support/xml_mini/nokogirisax.rb +2 -3
  178. data/lib/active_support/xml_mini/rexml.rb +6 -8
  179. data/lib/active_support/xml_mini.rb +35 -17
  180. data/lib/active_support.rb +8 -21
  181. metadata +104 -72
  182. data/lib/active_support/base64.rb +0 -54
  183. data/lib/active_support/core_ext/array/random_access.rb +0 -30
  184. data/lib/active_support/core_ext/date/freeze.rb +0 -33
  185. data/lib/active_support/core_ext/exception.rb +0 -3
  186. data/lib/active_support/core_ext/file/path.rb +0 -5
  187. data/lib/active_support/core_ext/float/rounding.rb +0 -19
  188. data/lib/active_support/core_ext/float.rb +0 -1
  189. data/lib/active_support/core_ext/hash/deep_dup.rb +0 -18
  190. data/lib/active_support/core_ext/io.rb +0 -15
  191. data/lib/active_support/core_ext/module/method_names.rb +0 -14
  192. data/lib/active_support/core_ext/module/synchronization.rb +0 -45
  193. data/lib/active_support/core_ext/process/daemon.rb +0 -23
  194. data/lib/active_support/core_ext/process.rb +0 -1
  195. data/lib/active_support/core_ext/range/blockless_step.rb +0 -29
  196. data/lib/active_support/core_ext/range/cover.rb +0 -3
  197. data/lib/active_support/core_ext/rexml.rb +0 -46
  198. data/lib/active_support/core_ext/string/interpolation.rb +0 -2
  199. data/lib/active_support/core_ext/string/xchar.rb +0 -18
  200. data/lib/active_support/core_ext/time/publicize_conversion_methods.rb +0 -10
  201. data/lib/active_support/memoizable.rb +0 -116
  202. data/lib/active_support/multibyte/exceptions.rb +0 -8
  203. data/lib/active_support/multibyte/utils.rb +0 -60
  204. data/lib/active_support/ruby/shim.rb +0 -22
  205. data/lib/active_support/security_utils.rb +0 -27
  206. data/lib/active_support/testing/mochaing.rb +0 -7
  207. data/lib/active_support/testing/performance/jruby.rb +0 -115
  208. data/lib/active_support/testing/performance/rubinius.rb +0 -113
  209. data/lib/active_support/testing/performance/ruby/mri.rb +0 -57
  210. data/lib/active_support/testing/performance/ruby/yarv.rb +0 -57
  211. data/lib/active_support/testing/performance/ruby.rb +0 -152
  212. data/lib/active_support/testing/performance.rb +0 -317
  213. data/lib/active_support/time/autoload.rb +0 -5
  214. data/lib/active_support/whiny_nil.rb +0 -24
@@ -3,7 +3,6 @@ require 'zlib'
3
3
  require 'active_support/core_ext/array/extract_options'
4
4
  require 'active_support/core_ext/array/wrap'
5
5
  require 'active_support/core_ext/benchmark'
6
- require 'active_support/core_ext/exception'
7
6
  require 'active_support/core_ext/class/attribute_accessors'
8
7
  require 'active_support/core_ext/numeric/bytes'
9
8
  require 'active_support/core_ext/numeric/time'
@@ -45,8 +44,8 @@ module ActiveSupport
45
44
  # Any additional arguments will be passed to the corresponding cache store
46
45
  # class's constructor:
47
46
  #
48
- # ActiveSupport::Cache.lookup_store(:file_store, "/tmp/cache")
49
- # # => same as: ActiveSupport::Cache::FileStore.new("/tmp/cache")
47
+ # ActiveSupport::Cache.lookup_store(:file_store, '/tmp/cache')
48
+ # # => same as: ActiveSupport::Cache::FileStore.new('/tmp/cache')
50
49
  #
51
50
  # If the first argument is not a Symbol, then it will simply be returned:
52
51
  #
@@ -57,16 +56,7 @@ module ActiveSupport
57
56
 
58
57
  case store
59
58
  when Symbol
60
- store_class_name = store.to_s.camelize
61
- store_class =
62
- begin
63
- require "active_support/cache/#{store}"
64
- rescue LoadError => e
65
- raise "Could not find cache store adapter for #{store} (#{e})"
66
- else
67
- ActiveSupport::Cache.const_get(store_class_name)
68
- end
69
- store_class.new(*parameters)
59
+ retrieve_store_class(store).new(*parameters)
70
60
  when nil
71
61
  ActiveSupport::Cache::MemoryStore.new
72
62
  else
@@ -74,6 +64,18 @@ module ActiveSupport
74
64
  end
75
65
  end
76
66
 
67
+ # Expands out the +key+ argument into a key that can be used for the
68
+ # cache store. Optionally accepts a namespace, and all keys will be
69
+ # scoped within that namespace.
70
+ #
71
+ # If the +key+ argument provided is an array, or responds to +to_a+, then
72
+ # each of elements in the array will be turned into parameters/keys and
73
+ # concatenated into a single key. For example:
74
+ #
75
+ # expand_cache_key([:foo, :bar]) # => "foo/bar"
76
+ # expand_cache_key([:foo, :bar], "namespace") # => "namespace/foo/bar"
77
+ #
78
+ # The +key+ argument can also respond to +cache_key+ or +to_param+.
77
79
  def expand_cache_key(key, namespace = nil)
78
80
  expanded_cache_key = namespace ? "#{namespace}/" : ""
79
81
 
@@ -91,9 +93,20 @@ module ActiveSupport
91
93
  case
92
94
  when key.respond_to?(:cache_key) then key.cache_key
93
95
  when key.is_a?(Array) then key.map { |element| retrieve_cache_key(element) }.to_param
96
+ when key.respond_to?(:to_a) then retrieve_cache_key(key.to_a)
94
97
  else key.to_param
95
98
  end.to_s
96
99
  end
100
+
101
+ # Obtains the specified cache store class, given the name of the +store+.
102
+ # Raises an error when the store class cannot be found.
103
+ def retrieve_store_class(store)
104
+ require "active_support/cache/#{store}"
105
+ rescue LoadError => e
106
+ raise "Could not find cache store adapter for #{store} (#{e})"
107
+ else
108
+ ActiveSupport::Cache.const_get(store.to_s.camelize)
109
+ end
97
110
  end
98
111
 
99
112
  # An abstract cache store class. There are multiple cache store
@@ -109,9 +122,9 @@ module ActiveSupport
109
122
  #
110
123
  # cache = ActiveSupport::Cache::MemoryStore.new
111
124
  #
112
- # cache.read("city") # => nil
113
- # cache.write("city", "Duckburgh")
114
- # cache.read("city") # => "Duckburgh"
125
+ # cache.read('city') # => nil
126
+ # cache.write('city', "Duckburgh")
127
+ # cache.read('city') # => "Duckburgh"
115
128
  #
116
129
  # Keys are always translated into Strings and are case sensitive. When an
117
130
  # object is specified as a key and has a +cache_key+ method defined, this
@@ -120,7 +133,7 @@ module ActiveSupport
120
133
  # elements will be delimited by slashes, and the elements within a Hash
121
134
  # will be sorted by key so they are consistent.
122
135
  #
123
- # cache.read("city") == cache.read(:city) # => true
136
+ # cache.read('city') == cache.read(:city) # => true
124
137
  #
125
138
  # Nil values can be cached.
126
139
  #
@@ -130,14 +143,13 @@ module ActiveSupport
130
143
  # is a Proc, it will be invoked when each key is evaluated so that you can
131
144
  # use application logic to invalidate keys.
132
145
  #
133
- # cache.namespace = lambda { @last_mod_time } # Set the namespace to a variable
146
+ # cache.namespace = -> { @last_mod_time } # Set the namespace to a variable
134
147
  # @last_mod_time = Time.now # Invalidate the entire cache by changing namespace
135
148
  #
136
- #
137
149
  # Caches can also store values in a compressed format to save space and
138
150
  # reduce time spent sending data. Since there is overhead, values must be
139
151
  # large enough to warrant compression. To turn on compression either pass
140
- # <tt>:compress => true</tt> in the initializer or as an option to +fetch+
152
+ # <tt>compress: true</tt> in the initializer or as an option to +fetch+
141
153
  # or +write+. To specify the threshold at which to compress values, set the
142
154
  # <tt>:compress_threshold</tt> option. The default threshold is 16K.
143
155
  class Store
@@ -147,8 +159,9 @@ module ActiveSupport
147
159
  attr_reader :silence, :options
148
160
  alias :silence? :silence
149
161
 
150
- # Create a new cache. The options will be passed to any write method calls except
151
- # for :namespace which can be used to set the global namespace for the cache.
162
+ # Create a new cache. The options will be passed to any write method calls
163
+ # except for <tt>:namespace</tt> which can be used to set the global
164
+ # namespace for the cache.
152
165
  def initialize(options = nil)
153
166
  @options = options ? options.dup : {}
154
167
  end
@@ -167,7 +180,8 @@ module ActiveSupport
167
180
  @silence = previous_silence
168
181
  end
169
182
 
170
- # Set to true if cache stores should be instrumented. Default is false.
183
+ # Set to +true+ if cache stores should be instrumented.
184
+ # Default is +false+.
171
185
  def self.instrument=(boolean)
172
186
  Thread.current[:instrument_cache_store] = boolean
173
187
  end
@@ -179,125 +193,109 @@ module ActiveSupport
179
193
  # Fetches data from the cache, using the given key. If there is data in
180
194
  # the cache with the given key, then that data is returned.
181
195
  #
182
- # If there is no such data in the cache (a cache miss), then nil will be
183
- # returned. However, if a block has been passed, that block will be run
184
- # in the event of a cache miss. The return value of the block will be
185
- # written to the cache under the given cache key, and that return value
186
- # will be returned.
196
+ # If there is no such data in the cache (a cache miss), then +nil+ will be
197
+ # returned. However, if a block has been passed, that block will be passed
198
+ # the key and executed in the event of a cache miss. The return value of the
199
+ # block will be written to the cache under the given cache key, and that
200
+ # return value will be returned.
187
201
  #
188
- # cache.write("today", "Monday")
189
- # cache.fetch("today") # => "Monday"
202
+ # cache.write('today', 'Monday')
203
+ # cache.fetch('today') # => "Monday"
190
204
  #
191
- # cache.fetch("city") # => nil
192
- # cache.fetch("city") do
193
- # "Duckburgh"
205
+ # cache.fetch('city') # => nil
206
+ # cache.fetch('city') do
207
+ # 'Duckburgh'
194
208
  # end
195
- # cache.fetch("city") # => "Duckburgh"
209
+ # cache.fetch('city') # => "Duckburgh"
196
210
  #
197
211
  # You may also specify additional options via the +options+ argument.
198
- # Setting <tt>:force => true</tt> will force a cache miss:
212
+ # Setting <tt>force: true</tt> will force a cache miss:
199
213
  #
200
- # cache.write("today", "Monday")
201
- # cache.fetch("today", :force => true) # => nil
214
+ # cache.write('today', 'Monday')
215
+ # cache.fetch('today', force: true) # => nil
202
216
  #
203
217
  # Setting <tt>:compress</tt> will store a large cache entry set by the call
204
218
  # in a compressed format.
205
219
  #
206
- #
207
220
  # Setting <tt>:expires_in</tt> will set an expiration time on the cache.
208
221
  # All caches support auto-expiring content after a specified number of
209
222
  # seconds. This value can be specified as an option to the constructor
210
223
  # (in which case all entries will be affected), or it can be supplied to
211
224
  # the +fetch+ or +write+ method to effect just one entry.
212
225
  #
213
- # cache = ActiveSupport::Cache::MemoryStore.new(:expires_in => 5.minutes)
214
- # cache.write(key, value, :expires_in => 1.minute) # Set a lower value for one entry
215
- #
216
- # Setting <tt>:race_condition_ttl</tt> is very useful in situations where a cache entry
217
- # is used very frequently and is under heavy load. If a cache expires and due to heavy load
218
- # seven different processes will try to read data natively and then they all will try to
219
- # write to cache. To avoid that case the first process to find an expired cache entry will
220
- # bump the cache expiration time by the value set in <tt>:race_condition_ttl</tt>. Yes
221
- # this process is extending the time for a stale value by another few seconds. Because
222
- # of extended life of the previous cache, other processes will continue to use slightly
223
- # stale data for a just a big longer. In the meantime that first process will go ahead
224
- # and will write into cache the new value. After that all the processes will start
225
- # getting new value. The key is to keep <tt>:race_condition_ttl</tt> small.
226
- #
227
- # If the process regenerating the entry errors out, the entry will be regenerated
228
- # after the specified number of seconds. Also note that the life of stale cache is
229
- # extended only if it expired recently. Otherwise a new value is generated and
230
- # <tt>:race_condition_ttl</tt> does not play any role.
226
+ # cache = ActiveSupport::Cache::MemoryStore.new(expires_in: 5.minutes)
227
+ # cache.write(key, value, expires_in: 1.minute) # Set a lower value for one entry
228
+ #
229
+ # Setting <tt>:race_condition_ttl</tt> is very useful in situations where
230
+ # a cache entry is used very frequently and is under heavy load. If a
231
+ # cache expires and due to heavy load seven different processes will try
232
+ # to read data natively and then they all will try to write to cache. To
233
+ # avoid that case the first process to find an expired cache entry will
234
+ # bump the cache expiration time by the value set in <tt>:race_condition_ttl</tt>.
235
+ # Yes, this process is extending the time for a stale value by another few
236
+ # seconds. Because of extended life of the previous cache, other processes
237
+ # will continue to use slightly stale data for a just a big longer. In the
238
+ # meantime that first process will go ahead and will write into cache the
239
+ # new value. After that all the processes will start getting new value.
240
+ # The key is to keep <tt>:race_condition_ttl</tt> small.
241
+ #
242
+ # If the process regenerating the entry errors out, the entry will be
243
+ # regenerated after the specified number of seconds. Also note that the
244
+ # life of stale cache is extended only if it expired recently. Otherwise
245
+ # a new value is generated and <tt>:race_condition_ttl</tt> does not play
246
+ # any role.
231
247
  #
232
248
  # # Set all values to expire after one minute.
233
- # cache = ActiveSupport::Cache::MemoryStore.new(:expires_in => 1.minute)
249
+ # cache = ActiveSupport::Cache::MemoryStore.new(expires_in: 1.minute)
234
250
  #
235
- # cache.write("foo", "original value")
251
+ # cache.write('foo', 'original value')
236
252
  # val_1 = nil
237
253
  # val_2 = nil
238
254
  # sleep 60
239
255
  #
240
256
  # Thread.new do
241
- # val_1 = cache.fetch("foo", :race_condition_ttl => 10) do
257
+ # val_1 = cache.fetch('foo', race_condition_ttl: 10) do
242
258
  # sleep 1
243
- # "new value 1"
259
+ # 'new value 1'
244
260
  # end
245
261
  # end
246
262
  #
247
263
  # Thread.new do
248
- # val_2 = cache.fetch("foo", :race_condition_ttl => 10) do
249
- # "new value 2"
264
+ # val_2 = cache.fetch('foo', race_condition_ttl: 10) do
265
+ # 'new value 2'
250
266
  # end
251
267
  # end
252
268
  #
253
269
  # # val_1 => "new value 1"
254
270
  # # val_2 => "original value"
255
271
  # # sleep 10 # First thread extend the life of cache by another 10 seconds
256
- # # cache.fetch("foo") => "new value 1"
272
+ # # cache.fetch('foo') => "new value 1"
257
273
  #
258
274
  # Other options will be handled by the specific cache store implementation.
259
- # Internally, #fetch calls #read_entry, and calls #write_entry on a cache miss.
260
- # +options+ will be passed to the #read and #write calls.
275
+ # Internally, #fetch calls #read_entry, and calls #write_entry on a cache
276
+ # miss. +options+ will be passed to the #read and #write calls.
261
277
  #
262
278
  # For example, MemCacheStore's #write method supports the +:raw+
263
279
  # option, which tells the memcached server to store all values as strings.
264
280
  # We can use this option with #fetch too:
265
281
  #
266
282
  # cache = ActiveSupport::Cache::MemCacheStore.new
267
- # cache.fetch("foo", :force => true, :raw => true) do
283
+ # cache.fetch("foo", force: true, raw: true) do
268
284
  # :bar
269
285
  # end
270
- # cache.fetch("foo") # => "bar"
286
+ # cache.fetch('foo') # => "bar"
271
287
  def fetch(name, options = nil)
272
288
  if block_given?
273
289
  options = merged_options(options)
274
290
  key = namespaced_key(name, options)
275
- unless options[:force]
276
- entry = instrument(:read, name, options) do |payload|
277
- payload[:super_operation] = :fetch if payload
278
- read_entry(key, options)
279
- end
280
- end
281
- if entry && entry.expired?
282
- race_ttl = options[:race_condition_ttl].to_f
283
- if race_ttl and Time.now.to_f - entry.expires_at <= race_ttl
284
- entry.expires_at = Time.now + race_ttl
285
- write_entry(key, entry, :expires_in => race_ttl * 2)
286
- else
287
- delete_entry(key, options)
288
- end
289
- entry = nil
290
- end
291
+
292
+ cached_entry = find_cached_entry(key, name, options) unless options[:force]
293
+ entry = handle_expired_entry(cached_entry, key, options)
291
294
 
292
295
  if entry
293
- instrument(:fetch_hit, name, options) { |payload| }
294
- entry.value
296
+ get_entry_value(entry, name, options)
295
297
  else
296
- result = instrument(:generate, name, options) do |payload|
297
- yield
298
- end
299
- write(name, result, options)
300
- result
298
+ save_block_result_to_cache(name, options) { |_name| yield _name }
301
299
  end
302
300
  else
303
301
  read(name, options)
@@ -306,7 +304,7 @@ module ActiveSupport
306
304
 
307
305
  # Fetches data from the cache, using the given key. If there is data in
308
306
  # the cache with the given key, then that data is returned. Otherwise,
309
- # nil is returned.
307
+ # +nil+ is returned.
310
308
  #
311
309
  # Options are passed to the underlying cache implementation.
312
310
  def read(name, options = nil)
@@ -359,7 +357,7 @@ module ActiveSupport
359
357
  # Options are passed to the underlying cache implementation.
360
358
  def write(name, value, options = nil)
361
359
  options = merged_options(options)
362
- instrument(:write, name, options) do |payload|
360
+ instrument(:write, name, options) do
363
361
  entry = Entry.new(value, options)
364
362
  write_entry(namespaced_key(name, options), entry, options)
365
363
  end
@@ -370,23 +368,19 @@ module ActiveSupport
370
368
  # Options are passed to the underlying cache implementation.
371
369
  def delete(name, options = nil)
372
370
  options = merged_options(options)
373
- instrument(:delete, name) do |payload|
371
+ instrument(:delete, name) do
374
372
  delete_entry(namespaced_key(name, options), options)
375
373
  end
376
374
  end
377
375
 
378
- # Return true if the cache contains an entry for the given key.
376
+ # Return +true+ if the cache contains an entry for the given key.
379
377
  #
380
378
  # Options are passed to the underlying cache implementation.
381
379
  def exist?(name, options = nil)
382
380
  options = merged_options(options)
383
- instrument(:exist?, name) do |payload|
381
+ instrument(:exist?, name) do
384
382
  entry = read_entry(namespaced_key(name, options), options)
385
- if entry && !entry.expired?
386
- true
387
- else
388
- false
389
- end
383
+ entry && !entry.expired?
390
384
  end
391
385
  end
392
386
 
@@ -408,7 +402,7 @@ module ActiveSupport
408
402
  raise NotImplementedError.new("#{self.class.name} does not support increment")
409
403
  end
410
404
 
411
- # Increment an integer value in the cache.
405
+ # Decrement an integer value in the cache.
412
406
  #
413
407
  # Options are passed to the underlying cache implementation.
414
408
  #
@@ -437,9 +431,10 @@ module ActiveSupport
437
431
  end
438
432
 
439
433
  protected
440
- # Add the namespace defined in the options to a pattern designed to match keys.
441
- # Implementations that support delete_matched should call this method to translate
442
- # a pattern that matches names into one that matches namespaced keys.
434
+ # Add the namespace defined in the options to a pattern designed to
435
+ # match keys. Implementations that support delete_matched should call
436
+ # this method to translate a pattern that matches names into one that
437
+ # matches namespaced keys.
443
438
  def key_matcher(pattern, options)
444
439
  prefix = options[:namespace].is_a?(Proc) ? options[:namespace].call : options[:namespace]
445
440
  if prefix
@@ -455,17 +450,20 @@ module ActiveSupport
455
450
  end
456
451
  end
457
452
 
458
- # Read an entry from the cache implementation. Subclasses must implement this method.
453
+ # Read an entry from the cache implementation. Subclasses must implement
454
+ # this method.
459
455
  def read_entry(key, options) # :nodoc:
460
456
  raise NotImplementedError.new
461
457
  end
462
458
 
463
- # Write an entry to the cache implementation. Subclasses must implement this method.
459
+ # Write an entry to the cache implementation. Subclasses must implement
460
+ # this method.
464
461
  def write_entry(key, entry, options) # :nodoc:
465
462
  raise NotImplementedError.new
466
463
  end
467
464
 
468
- # Delete an entry from the cache implementation. Subclasses must implement this method.
465
+ # Delete an entry from the cache implementation. Subclasses must
466
+ # implement this method.
469
467
  def delete_entry(key, options) # :nodoc:
470
468
  raise NotImplementedError.new
471
469
  end
@@ -481,7 +479,7 @@ module ActiveSupport
481
479
  end
482
480
 
483
481
  # Expand key to be a consistent string value. Invoke +cache_key+ if
484
- # object responds to +cache_key+. Otherwise, to_param method will be
482
+ # object responds to +cache_key+. Otherwise, +to_param+ method will be
485
483
  # called. If the key is a Hash, then keys will be sorted alphabetically.
486
484
  def expanded_key(key) # :nodoc:
487
485
  return key.cache_key.to_s if key.respond_to?(:cache_key)
@@ -500,7 +498,8 @@ module ActiveSupport
500
498
  key.to_param
501
499
  end
502
500
 
503
- # Prefix a key with the namespace. Namespace and key will be delimited with a colon.
501
+ # Prefix a key with the namespace. Namespace and key will be delimited
502
+ # with a colon.
504
503
  def namespaced_key(key, options)
505
504
  key = expanded_key(key)
506
505
  namespace = options[:namespace] if options
@@ -525,114 +524,160 @@ module ActiveSupport
525
524
  return unless logger && logger.debug? && !silence?
526
525
  logger.debug("Cache #{operation}: #{key}#{options.blank? ? "" : " (#{options.inspect})"}")
527
526
  end
528
- end
529
527
 
530
- # Entry that is put into caches. It supports expiration time on entries and can compress values
531
- # to save space in the cache.
532
- class Entry
533
- attr_reader :created_at, :expires_in
534
-
535
- DEFAULT_COMPRESS_LIMIT = 16.kilobytes
528
+ def find_cached_entry(key, name, options)
529
+ instrument(:read, name, options) do |payload|
530
+ payload[:super_operation] = :fetch if payload
531
+ read_entry(key, options)
532
+ end
533
+ end
536
534
 
537
- class << self
538
- # Create an entry with internal attributes set. This method is intended to be
539
- # used by implementations that store cache entries in a native format instead
540
- # of as serialized Ruby objects.
541
- def create(raw_value, created_at, options = {})
542
- entry = new(nil)
543
- entry.instance_variable_set(:@value, raw_value)
544
- entry.instance_variable_set(:@created_at, created_at.to_f)
545
- entry.instance_variable_set(:@compressed, options[:compressed])
546
- entry.instance_variable_set(:@expires_in, options[:expires_in])
535
+ def handle_expired_entry(entry, key, options)
536
+ if entry && entry.expired?
537
+ race_ttl = options[:race_condition_ttl].to_i
538
+ if race_ttl && (Time.now.to_f - entry.expires_at <= race_ttl)
539
+ # When an entry has :race_condition_ttl defined, put the stale entry back into the cache
540
+ # for a brief period while the entry is begin recalculated.
541
+ entry.expires_at = Time.now + race_ttl
542
+ write_entry(key, entry, :expires_in => race_ttl * 2)
543
+ else
544
+ delete_entry(key, options)
545
+ end
546
+ entry = nil
547
+ end
547
548
  entry
548
549
  end
549
- end
550
+
551
+ def get_entry_value(entry, name, options)
552
+ instrument(:fetch_hit, name, options) { |payload| }
553
+ entry.value
554
+ end
555
+
556
+ def save_block_result_to_cache(name, options)
557
+ result = instrument(:generate, name, options) do |payload|
558
+ yield(name)
559
+ end
560
+ write(name, result, options)
561
+ result
562
+ end
563
+ end
564
+
565
+ # This class is used to represent cache entries. Cache entries have a value and an optional
566
+ # expiration time. The expiration time is used to support the :race_condition_ttl option
567
+ # on the cache.
568
+ #
569
+ # Since cache entries in most instances will be serialized, the internals of this class are highly optimized
570
+ # using short instance variable names that are lazily defined.
571
+ class Entry # :nodoc:
572
+ DEFAULT_COMPRESS_LIMIT = 16.kilobytes
550
573
 
551
574
  # Create a new cache entry for the specified value. Options supported are
552
575
  # +:compress+, +:compress_threshold+, and +:expires_in+.
553
576
  def initialize(value, options = {})
554
- @compressed = false
555
- @expires_in = options[:expires_in]
556
- @expires_in = @expires_in.to_f if @expires_in
557
- @created_at = Time.now.to_f
558
- if value.nil?
559
- @value = nil
577
+ if should_compress?(value, options)
578
+ @value = compress(value)
579
+ @compressed = true
560
580
  else
561
- @value = Marshal.dump(value)
562
- if should_compress?(@value, options)
563
- @value = Zlib::Deflate.deflate(@value)
564
- @compressed = true
565
- end
581
+ @value = value
566
582
  end
583
+ @created_at = Time.now.to_f
584
+ @expires_in = options[:expires_in]
585
+ @expires_in = @expires_in.to_f if @expires_in
567
586
  end
568
587
 
569
- # Get the raw value. This value may be serialized and compressed.
570
- def raw_value
571
- @value
572
- end
573
-
574
- # Get the value stored in the cache.
575
588
  def value
576
- # If the original value was exactly false @value is still true because
577
- # it is marshalled and eventually compressed. Both operations yield
578
- # strings.
579
- if @value
580
- # In rails 3.1 and earlier values in entries did not marshaled without
581
- # options[:compress] and if it's Numeric.
582
- # But after commit a263f377978fc07515b42808ebc1f7894fafaa3a
583
- # all values in entries are marshalled. And after that code below expects
584
- # that all values in entries will be marshaled (and will be strings).
585
- # So here we need a check for old ones.
586
- begin
587
- Marshal.load(compressed? ? Zlib::Inflate.inflate(@value) : @value)
588
- rescue TypeError
589
- compressed? ? Zlib::Inflate.inflate(@value) : @value
590
- end
591
- end
592
- end
593
-
594
- def compressed?
595
- @compressed
589
+ convert_version_4beta1_entry! if defined?(@v)
590
+ compressed? ? uncompress(@value) : @value
596
591
  end
597
592
 
598
- # Check if the entry is expired. The +expires_in+ parameter can override the
599
- # value set when the entry was created.
593
+ # Check if the entry is expired. The +expires_in+ parameter can override
594
+ # the value set when the entry was created.
600
595
  def expired?
596
+ convert_version_4beta1_entry! if defined?(@value)
601
597
  @expires_in && @created_at + @expires_in <= Time.now.to_f
602
598
  end
603
599
 
604
- # Set a new time when the entry will expire.
605
- def expires_at=(time)
606
- if time
607
- @expires_in = time.to_f - @created_at
600
+ def expires_at
601
+ @expires_in ? @created_at + @expires_in : nil
602
+ end
603
+
604
+ def expires_at=(value)
605
+ if value
606
+ @expires_in = value.to_f - @created_at
608
607
  else
609
608
  @expires_in = nil
610
609
  end
611
610
  end
612
611
 
613
- # Seconds since the epoch when the entry will expire.
614
- def expires_at
615
- @expires_in ? @created_at + @expires_in : nil
616
- end
617
-
618
- # Returns the size of the cached value. This could be less than value.size
619
- # if the data is compressed.
612
+ # Returns the size of the cached value. This could be less than
613
+ # <tt>value.size</tt> if the data is compressed.
620
614
  def size
621
- if @value.nil?
622
- 0
615
+ if defined?(@s)
616
+ @s
623
617
  else
624
- @value.bytesize
618
+ case value
619
+ when NilClass
620
+ 0
621
+ when String
622
+ @value.bytesize
623
+ else
624
+ @s = Marshal.dump(@value).bytesize
625
+ end
626
+ end
627
+ end
628
+
629
+ # Duplicate the value in a class. This is used by cache implementations that don't natively
630
+ # serialize entries to protect against accidental cache modifications.
631
+ def dup_value!
632
+ convert_version_4beta1_entry! if defined?(@v)
633
+ if @value && !compressed? && !(@value.is_a?(Numeric) || @value == true || @value == false)
634
+ if @value.is_a?(String)
635
+ @value = @value.dup
636
+ else
637
+ @value = Marshal.load(Marshal.dump(@value))
638
+ end
625
639
  end
626
640
  end
627
641
 
628
642
  private
629
- def should_compress?(serialized_value, options)
630
- if options[:compress]
643
+ def should_compress?(value, options)
644
+ if value && options[:compress]
631
645
  compress_threshold = options[:compress_threshold] || DEFAULT_COMPRESS_LIMIT
632
- return true if serialized_value.size >= compress_threshold
646
+ serialized_value_size = (value.is_a?(String) ? value : Marshal.dump(value)).bytesize
647
+ return true if serialized_value_size >= compress_threshold
633
648
  end
634
649
  false
635
650
  end
651
+
652
+ def compressed?
653
+ defined?(@compressed) ? @compressed : false
654
+ end
655
+
656
+ def compress(value)
657
+ Zlib::Deflate.deflate(Marshal.dump(value))
658
+ end
659
+
660
+ def uncompress(value)
661
+ Marshal.load(Zlib::Inflate.inflate(value))
662
+ end
663
+
664
+ # The internals of this method changed between Rails 3.x and 4.0. This method provides the glue
665
+ # to ensure that cache entries created under the old version still work with the new class definition.
666
+ def convert_version_4beta1_entry!
667
+ if defined?(@v)
668
+ @value = @v
669
+ remove_instance_variable(:@v)
670
+ end
671
+ if defined?(@c)
672
+ @compressed = @c
673
+ remove_instance_variable(:@c)
674
+ end
675
+ if defined?(@x) && @x
676
+ @created_at ||= Time.now.to_f
677
+ @expires_in = @x - @created_at
678
+ remove_instance_variable(:@x)
679
+ end
680
+ end
636
681
  end
637
682
  end
638
683
  end