activesupport 5.0.7.1

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 (236) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +1013 -0
  3. data/MIT-LICENSE +20 -0
  4. data/README.rdoc +39 -0
  5. data/lib/active_support.rb +99 -0
  6. data/lib/active_support/all.rb +3 -0
  7. data/lib/active_support/array_inquirer.rb +44 -0
  8. data/lib/active_support/backtrace_cleaner.rb +103 -0
  9. data/lib/active_support/benchmarkable.rb +49 -0
  10. data/lib/active_support/builder.rb +6 -0
  11. data/lib/active_support/cache.rb +701 -0
  12. data/lib/active_support/cache/file_store.rb +204 -0
  13. data/lib/active_support/cache/mem_cache_store.rb +207 -0
  14. data/lib/active_support/cache/memory_store.rb +167 -0
  15. data/lib/active_support/cache/null_store.rb +41 -0
  16. data/lib/active_support/cache/strategy/local_cache.rb +172 -0
  17. data/lib/active_support/cache/strategy/local_cache_middleware.rb +44 -0
  18. data/lib/active_support/callbacks.rb +791 -0
  19. data/lib/active_support/concern.rb +142 -0
  20. data/lib/active_support/concurrency/latch.rb +26 -0
  21. data/lib/active_support/concurrency/share_lock.rb +226 -0
  22. data/lib/active_support/configurable.rb +148 -0
  23. data/lib/active_support/core_ext.rb +4 -0
  24. data/lib/active_support/core_ext/array.rb +7 -0
  25. data/lib/active_support/core_ext/array/access.rb +90 -0
  26. data/lib/active_support/core_ext/array/conversions.rb +211 -0
  27. data/lib/active_support/core_ext/array/extract_options.rb +29 -0
  28. data/lib/active_support/core_ext/array/grouping.rb +107 -0
  29. data/lib/active_support/core_ext/array/inquiry.rb +17 -0
  30. data/lib/active_support/core_ext/array/prepend_and_append.rb +7 -0
  31. data/lib/active_support/core_ext/array/wrap.rb +46 -0
  32. data/lib/active_support/core_ext/benchmark.rb +14 -0
  33. data/lib/active_support/core_ext/big_decimal.rb +1 -0
  34. data/lib/active_support/core_ext/big_decimal/conversions.rb +14 -0
  35. data/lib/active_support/core_ext/class.rb +2 -0
  36. data/lib/active_support/core_ext/class/attribute.rb +128 -0
  37. data/lib/active_support/core_ext/class/attribute_accessors.rb +4 -0
  38. data/lib/active_support/core_ext/class/subclasses.rb +41 -0
  39. data/lib/active_support/core_ext/date.rb +5 -0
  40. data/lib/active_support/core_ext/date/acts_like.rb +8 -0
  41. data/lib/active_support/core_ext/date/blank.rb +12 -0
  42. data/lib/active_support/core_ext/date/calculations.rb +143 -0
  43. data/lib/active_support/core_ext/date/conversions.rb +95 -0
  44. data/lib/active_support/core_ext/date/zones.rb +6 -0
  45. data/lib/active_support/core_ext/date_and_time/calculations.rb +335 -0
  46. data/lib/active_support/core_ext/date_and_time/compatibility.rb +14 -0
  47. data/lib/active_support/core_ext/date_and_time/zones.rb +40 -0
  48. data/lib/active_support/core_ext/date_time.rb +5 -0
  49. data/lib/active_support/core_ext/date_time/acts_like.rb +14 -0
  50. data/lib/active_support/core_ext/date_time/blank.rb +12 -0
  51. data/lib/active_support/core_ext/date_time/calculations.rb +199 -0
  52. data/lib/active_support/core_ext/date_time/compatibility.rb +16 -0
  53. data/lib/active_support/core_ext/date_time/conversions.rb +105 -0
  54. data/lib/active_support/core_ext/digest/uuid.rb +51 -0
  55. data/lib/active_support/core_ext/enumerable.rb +146 -0
  56. data/lib/active_support/core_ext/file.rb +1 -0
  57. data/lib/active_support/core_ext/file/atomic.rb +68 -0
  58. data/lib/active_support/core_ext/hash.rb +9 -0
  59. data/lib/active_support/core_ext/hash/compact.rb +24 -0
  60. data/lib/active_support/core_ext/hash/conversions.rb +262 -0
  61. data/lib/active_support/core_ext/hash/deep_merge.rb +38 -0
  62. data/lib/active_support/core_ext/hash/except.rb +22 -0
  63. data/lib/active_support/core_ext/hash/indifferent_access.rb +23 -0
  64. data/lib/active_support/core_ext/hash/keys.rb +170 -0
  65. data/lib/active_support/core_ext/hash/reverse_merge.rb +22 -0
  66. data/lib/active_support/core_ext/hash/slice.rb +48 -0
  67. data/lib/active_support/core_ext/hash/transform_values.rb +29 -0
  68. data/lib/active_support/core_ext/integer.rb +3 -0
  69. data/lib/active_support/core_ext/integer/inflections.rb +29 -0
  70. data/lib/active_support/core_ext/integer/multiple.rb +10 -0
  71. data/lib/active_support/core_ext/integer/time.rb +29 -0
  72. data/lib/active_support/core_ext/kernel.rb +4 -0
  73. data/lib/active_support/core_ext/kernel/agnostics.rb +11 -0
  74. data/lib/active_support/core_ext/kernel/concern.rb +12 -0
  75. data/lib/active_support/core_ext/kernel/debugger.rb +3 -0
  76. data/lib/active_support/core_ext/kernel/reporting.rb +43 -0
  77. data/lib/active_support/core_ext/kernel/singleton_class.rb +6 -0
  78. data/lib/active_support/core_ext/load_error.rb +31 -0
  79. data/lib/active_support/core_ext/marshal.rb +22 -0
  80. data/lib/active_support/core_ext/module.rb +12 -0
  81. data/lib/active_support/core_ext/module/aliasing.rb +74 -0
  82. data/lib/active_support/core_ext/module/anonymous.rb +28 -0
  83. data/lib/active_support/core_ext/module/attr_internal.rb +36 -0
  84. data/lib/active_support/core_ext/module/attribute_accessors.rb +212 -0
  85. data/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb +141 -0
  86. data/lib/active_support/core_ext/module/concerning.rb +135 -0
  87. data/lib/active_support/core_ext/module/delegation.rb +216 -0
  88. data/lib/active_support/core_ext/module/deprecation.rb +23 -0
  89. data/lib/active_support/core_ext/module/introspection.rb +68 -0
  90. data/lib/active_support/core_ext/module/method_transplanting.rb +3 -0
  91. data/lib/active_support/core_ext/module/qualified_const.rb +70 -0
  92. data/lib/active_support/core_ext/module/reachable.rb +8 -0
  93. data/lib/active_support/core_ext/module/remove_method.rb +35 -0
  94. data/lib/active_support/core_ext/name_error.rb +31 -0
  95. data/lib/active_support/core_ext/numeric.rb +4 -0
  96. data/lib/active_support/core_ext/numeric/bytes.rb +64 -0
  97. data/lib/active_support/core_ext/numeric/conversions.rb +144 -0
  98. data/lib/active_support/core_ext/numeric/inquiry.rb +26 -0
  99. data/lib/active_support/core_ext/numeric/time.rb +74 -0
  100. data/lib/active_support/core_ext/object.rb +14 -0
  101. data/lib/active_support/core_ext/object/acts_like.rb +10 -0
  102. data/lib/active_support/core_ext/object/blank.rb +143 -0
  103. data/lib/active_support/core_ext/object/conversions.rb +4 -0
  104. data/lib/active_support/core_ext/object/deep_dup.rb +53 -0
  105. data/lib/active_support/core_ext/object/duplicable.rb +124 -0
  106. data/lib/active_support/core_ext/object/inclusion.rb +27 -0
  107. data/lib/active_support/core_ext/object/instance_variables.rb +28 -0
  108. data/lib/active_support/core_ext/object/json.rb +205 -0
  109. data/lib/active_support/core_ext/object/to_param.rb +1 -0
  110. data/lib/active_support/core_ext/object/to_query.rb +84 -0
  111. data/lib/active_support/core_ext/object/try.rb +146 -0
  112. data/lib/active_support/core_ext/object/with_options.rb +69 -0
  113. data/lib/active_support/core_ext/range.rb +4 -0
  114. data/lib/active_support/core_ext/range/conversions.rb +31 -0
  115. data/lib/active_support/core_ext/range/each.rb +21 -0
  116. data/lib/active_support/core_ext/range/include_range.rb +23 -0
  117. data/lib/active_support/core_ext/range/overlaps.rb +8 -0
  118. data/lib/active_support/core_ext/regexp.rb +5 -0
  119. data/lib/active_support/core_ext/securerandom.rb +23 -0
  120. data/lib/active_support/core_ext/string.rb +13 -0
  121. data/lib/active_support/core_ext/string/access.rb +104 -0
  122. data/lib/active_support/core_ext/string/behavior.rb +6 -0
  123. data/lib/active_support/core_ext/string/conversions.rb +57 -0
  124. data/lib/active_support/core_ext/string/exclude.rb +11 -0
  125. data/lib/active_support/core_ext/string/filters.rb +102 -0
  126. data/lib/active_support/core_ext/string/indent.rb +43 -0
  127. data/lib/active_support/core_ext/string/inflections.rb +244 -0
  128. data/lib/active_support/core_ext/string/inquiry.rb +13 -0
  129. data/lib/active_support/core_ext/string/multibyte.rb +53 -0
  130. data/lib/active_support/core_ext/string/output_safety.rb +260 -0
  131. data/lib/active_support/core_ext/string/starts_ends_with.rb +4 -0
  132. data/lib/active_support/core_ext/string/strip.rb +23 -0
  133. data/lib/active_support/core_ext/string/zones.rb +14 -0
  134. data/lib/active_support/core_ext/struct.rb +3 -0
  135. data/lib/active_support/core_ext/time.rb +5 -0
  136. data/lib/active_support/core_ext/time/acts_like.rb +8 -0
  137. data/lib/active_support/core_ext/time/calculations.rb +290 -0
  138. data/lib/active_support/core_ext/time/compatibility.rb +14 -0
  139. data/lib/active_support/core_ext/time/conversions.rb +67 -0
  140. data/lib/active_support/core_ext/time/marshal.rb +3 -0
  141. data/lib/active_support/core_ext/time/zones.rb +111 -0
  142. data/lib/active_support/core_ext/uri.rb +24 -0
  143. data/lib/active_support/dependencies.rb +755 -0
  144. data/lib/active_support/dependencies/autoload.rb +77 -0
  145. data/lib/active_support/dependencies/interlock.rb +55 -0
  146. data/lib/active_support/deprecation.rb +43 -0
  147. data/lib/active_support/deprecation/behaviors.rb +90 -0
  148. data/lib/active_support/deprecation/instance_delegator.rb +37 -0
  149. data/lib/active_support/deprecation/method_wrappers.rb +70 -0
  150. data/lib/active_support/deprecation/proxy_wrappers.rb +149 -0
  151. data/lib/active_support/deprecation/reporting.rb +112 -0
  152. data/lib/active_support/descendants_tracker.rb +60 -0
  153. data/lib/active_support/duration.rb +235 -0
  154. data/lib/active_support/duration/iso8601_parser.rb +122 -0
  155. data/lib/active_support/duration/iso8601_serializer.rb +51 -0
  156. data/lib/active_support/evented_file_update_checker.rb +199 -0
  157. data/lib/active_support/execution_wrapper.rb +126 -0
  158. data/lib/active_support/executor.rb +6 -0
  159. data/lib/active_support/file_update_checker.rb +157 -0
  160. data/lib/active_support/gem_version.rb +15 -0
  161. data/lib/active_support/gzip.rb +36 -0
  162. data/lib/active_support/hash_with_indifferent_access.rb +329 -0
  163. data/lib/active_support/i18n.rb +13 -0
  164. data/lib/active_support/i18n_railtie.rb +115 -0
  165. data/lib/active_support/inflections.rb +70 -0
  166. data/lib/active_support/inflector.rb +7 -0
  167. data/lib/active_support/inflector/inflections.rb +242 -0
  168. data/lib/active_support/inflector/methods.rb +390 -0
  169. data/lib/active_support/inflector/transliterate.rb +112 -0
  170. data/lib/active_support/json.rb +2 -0
  171. data/lib/active_support/json/decoding.rb +74 -0
  172. data/lib/active_support/json/encoding.rb +127 -0
  173. data/lib/active_support/key_generator.rb +71 -0
  174. data/lib/active_support/lazy_load_hooks.rb +76 -0
  175. data/lib/active_support/locale/en.yml +135 -0
  176. data/lib/active_support/log_subscriber.rb +109 -0
  177. data/lib/active_support/log_subscriber/test_helper.rb +104 -0
  178. data/lib/active_support/logger.rb +106 -0
  179. data/lib/active_support/logger_silence.rb +28 -0
  180. data/lib/active_support/logger_thread_safe_level.rb +31 -0
  181. data/lib/active_support/message_encryptor.rb +114 -0
  182. data/lib/active_support/message_verifier.rb +134 -0
  183. data/lib/active_support/multibyte.rb +21 -0
  184. data/lib/active_support/multibyte/chars.rb +231 -0
  185. data/lib/active_support/multibyte/unicode.rb +413 -0
  186. data/lib/active_support/notifications.rb +212 -0
  187. data/lib/active_support/notifications/fanout.rb +157 -0
  188. data/lib/active_support/notifications/instrumenter.rb +91 -0
  189. data/lib/active_support/number_helper.rb +368 -0
  190. data/lib/active_support/number_helper/number_converter.rb +182 -0
  191. data/lib/active_support/number_helper/number_to_currency_converter.rb +44 -0
  192. data/lib/active_support/number_helper/number_to_delimited_converter.rb +28 -0
  193. data/lib/active_support/number_helper/number_to_human_converter.rb +68 -0
  194. data/lib/active_support/number_helper/number_to_human_size_converter.rb +62 -0
  195. data/lib/active_support/number_helper/number_to_percentage_converter.rb +12 -0
  196. data/lib/active_support/number_helper/number_to_phone_converter.rb +58 -0
  197. data/lib/active_support/number_helper/number_to_rounded_converter.rb +92 -0
  198. data/lib/active_support/option_merger.rb +25 -0
  199. data/lib/active_support/ordered_hash.rb +48 -0
  200. data/lib/active_support/ordered_options.rb +81 -0
  201. data/lib/active_support/per_thread_registry.rb +58 -0
  202. data/lib/active_support/proxy_object.rb +13 -0
  203. data/lib/active_support/rails.rb +27 -0
  204. data/lib/active_support/railtie.rb +51 -0
  205. data/lib/active_support/reloader.rb +129 -0
  206. data/lib/active_support/rescuable.rb +173 -0
  207. data/lib/active_support/security_utils.rb +27 -0
  208. data/lib/active_support/string_inquirer.rb +26 -0
  209. data/lib/active_support/subscriber.rb +120 -0
  210. data/lib/active_support/tagged_logging.rb +77 -0
  211. data/lib/active_support/test_case.rb +88 -0
  212. data/lib/active_support/testing/assertions.rb +99 -0
  213. data/lib/active_support/testing/autorun.rb +5 -0
  214. data/lib/active_support/testing/constant_lookup.rb +50 -0
  215. data/lib/active_support/testing/declarative.rb +26 -0
  216. data/lib/active_support/testing/deprecation.rb +36 -0
  217. data/lib/active_support/testing/file_fixtures.rb +34 -0
  218. data/lib/active_support/testing/isolation.rb +115 -0
  219. data/lib/active_support/testing/method_call_assertions.rb +41 -0
  220. data/lib/active_support/testing/setup_and_teardown.rb +50 -0
  221. data/lib/active_support/testing/stream.rb +42 -0
  222. data/lib/active_support/testing/tagged_logging.rb +25 -0
  223. data/lib/active_support/testing/time_helpers.rb +136 -0
  224. data/lib/active_support/time.rb +18 -0
  225. data/lib/active_support/time_with_zone.rb +511 -0
  226. data/lib/active_support/values/time_zone.rb +484 -0
  227. data/lib/active_support/values/unicode_tables.dat +0 -0
  228. data/lib/active_support/version.rb +8 -0
  229. data/lib/active_support/xml_mini.rb +209 -0
  230. data/lib/active_support/xml_mini/jdom.rb +181 -0
  231. data/lib/active_support/xml_mini/libxml.rb +77 -0
  232. data/lib/active_support/xml_mini/libxmlsax.rb +82 -0
  233. data/lib/active_support/xml_mini/nokogiri.rb +81 -0
  234. data/lib/active_support/xml_mini/nokogirisax.rb +85 -0
  235. data/lib/active_support/xml_mini/rexml.rb +128 -0
  236. metadata +350 -0
@@ -0,0 +1,204 @@
1
+ require 'active_support/core_ext/marshal'
2
+ require 'active_support/core_ext/file/atomic'
3
+ require 'active_support/core_ext/string/conversions'
4
+ require 'uri/common'
5
+
6
+ module ActiveSupport
7
+ module Cache
8
+ # A cache store implementation which stores everything on the filesystem.
9
+ #
10
+ # FileStore implements the Strategy::LocalCache strategy which implements
11
+ # an in-memory cache inside of a block.
12
+ class FileStore < Store
13
+ prepend Strategy::LocalCache
14
+ attr_reader :cache_path
15
+
16
+ DIR_FORMATTER = "%03X"
17
+ FILENAME_MAX_SIZE = 228 # max filename size on file system is 255, minus room for timestamp and random characters appended by Tempfile (used by atomic write)
18
+ FILEPATH_MAX_SIZE = 900 # max is 1024, plus some room
19
+ EXCLUDED_DIRS = ['.', '..'].freeze
20
+ GITKEEP_FILES = ['.gitkeep', '.keep'].freeze
21
+
22
+ def initialize(cache_path, options = nil)
23
+ super(options)
24
+ @cache_path = cache_path.to_s
25
+ end
26
+
27
+ # Deletes all items from the cache. In this case it deletes all the entries in the specified
28
+ # file store directory except for .keep or .gitkeep. Be careful which directory is specified in your
29
+ # config file when using +FileStore+ because everything in that directory will be deleted.
30
+ def clear(options = nil)
31
+ root_dirs = exclude_from(cache_path, EXCLUDED_DIRS + GITKEEP_FILES)
32
+ FileUtils.rm_r(root_dirs.collect{|f| File.join(cache_path, f)})
33
+ rescue Errno::ENOENT
34
+ end
35
+
36
+ # Preemptively iterates through all stored keys and removes the ones which have expired.
37
+ def cleanup(options = nil)
38
+ options = merged_options(options)
39
+ search_dir(cache_path) do |fname|
40
+ key = file_path_key(fname)
41
+ entry = read_entry(key, options)
42
+ delete_entry(key, options) if entry && entry.expired?
43
+ end
44
+ end
45
+
46
+ # Increments an already existing integer value that is stored in the cache.
47
+ # If the key is not found nothing is done.
48
+ def increment(name, amount = 1, options = nil)
49
+ modify_value(name, amount, options)
50
+ end
51
+
52
+ # Decrements an already existing integer value that is stored in the cache.
53
+ # If the key is not found nothing is done.
54
+ def decrement(name, amount = 1, options = nil)
55
+ modify_value(name, -amount, options)
56
+ end
57
+
58
+ def delete_matched(matcher, options = nil)
59
+ options = merged_options(options)
60
+ instrument(:delete_matched, matcher.inspect) do
61
+ matcher = key_matcher(matcher, options)
62
+ search_dir(cache_path) do |path|
63
+ key = file_path_key(path)
64
+ delete_entry(path, options) if key.match(matcher)
65
+ end
66
+ end
67
+ end
68
+
69
+ protected
70
+
71
+ def read_entry(key, options)
72
+ if File.exist?(key)
73
+ File.open(key) { |f| Marshal.load(f) }
74
+ end
75
+ rescue => e
76
+ logger.error("FileStoreError (#{e}): #{e.message}") if logger
77
+ nil
78
+ end
79
+
80
+ def write_entry(key, entry, options)
81
+ return false if options[:unless_exist] && File.exist?(key)
82
+ ensure_cache_path(File.dirname(key))
83
+ File.atomic_write(key, cache_path) {|f| Marshal.dump(entry, f)}
84
+ true
85
+ end
86
+
87
+ def delete_entry(key, options)
88
+ if File.exist?(key)
89
+ begin
90
+ File.delete(key)
91
+ delete_empty_directories(File.dirname(key))
92
+ true
93
+ rescue => e
94
+ # Just in case the error was caused by another process deleting the file first.
95
+ raise e if File.exist?(key)
96
+ false
97
+ end
98
+ end
99
+ end
100
+
101
+ private
102
+ # Lock a file for a block so only one process can modify it at a time.
103
+ def lock_file(file_name, &block) # :nodoc:
104
+ if File.exist?(file_name)
105
+ File.open(file_name, 'r+') do |f|
106
+ begin
107
+ f.flock File::LOCK_EX
108
+ yield
109
+ ensure
110
+ f.flock File::LOCK_UN
111
+ end
112
+ end
113
+ else
114
+ yield
115
+ end
116
+ end
117
+
118
+ # Translate a key into a file path.
119
+ def normalize_key(key, options)
120
+ key = super
121
+ fname = URI.encode_www_form_component(key)
122
+
123
+ if fname.size > FILEPATH_MAX_SIZE
124
+ fname = Digest::MD5.hexdigest(key)
125
+ end
126
+
127
+ hash = Zlib.adler32(fname)
128
+ hash, dir_1 = hash.divmod(0x1000)
129
+ dir_2 = hash.modulo(0x1000)
130
+ fname_paths = []
131
+
132
+ # Make sure file name doesn't exceed file system limits.
133
+ begin
134
+ fname_paths << fname[0, FILENAME_MAX_SIZE]
135
+ fname = fname[FILENAME_MAX_SIZE..-1]
136
+ end until fname.blank?
137
+
138
+ File.join(cache_path, DIR_FORMATTER % dir_1, DIR_FORMATTER % dir_2, *fname_paths)
139
+ end
140
+
141
+ def key_file_path(key)
142
+ ActiveSupport::Deprecation.warn(<<-MESSAGE.strip_heredoc)
143
+ `key_file_path` is deprecated and will be removed from Rails 5.1.
144
+ Please use `normalize_key` which will return a fully resolved key or nothing.
145
+ MESSAGE
146
+ key
147
+ end
148
+
149
+ # Translate a file path into a key.
150
+ def file_path_key(path)
151
+ fname = path[cache_path.to_s.size..-1].split(File::SEPARATOR, 4).last
152
+ URI.decode_www_form_component(fname, Encoding::UTF_8)
153
+ end
154
+
155
+ # Delete empty directories in the cache.
156
+ def delete_empty_directories(dir)
157
+ return if File.realpath(dir) == File.realpath(cache_path)
158
+ if exclude_from(dir, EXCLUDED_DIRS).empty?
159
+ Dir.delete(dir) rescue nil
160
+ delete_empty_directories(File.dirname(dir))
161
+ end
162
+ end
163
+
164
+ # Make sure a file path's directories exist.
165
+ def ensure_cache_path(path)
166
+ FileUtils.makedirs(path) unless File.exist?(path)
167
+ end
168
+
169
+ def search_dir(dir, &callback)
170
+ return if !File.exist?(dir)
171
+ Dir.foreach(dir) do |d|
172
+ next if EXCLUDED_DIRS.include?(d)
173
+ name = File.join(dir, d)
174
+ if File.directory?(name)
175
+ search_dir(name, &callback)
176
+ else
177
+ callback.call name
178
+ end
179
+ end
180
+ end
181
+
182
+ # Modifies the amount of an already existing integer value that is stored in the cache.
183
+ # If the key is not found nothing is done.
184
+ def modify_value(name, amount, options)
185
+ file_name = normalize_key(name, options)
186
+
187
+ lock_file(file_name) do
188
+ options = merged_options(options)
189
+
190
+ if num = read(name, options)
191
+ num = num.to_i + amount
192
+ write(name, num, options)
193
+ num
194
+ end
195
+ end
196
+ end
197
+
198
+ # Exclude entries from source directory
199
+ def exclude_from(source, excludes)
200
+ Dir.entries(source).reject { |f| excludes.include?(f) }
201
+ end
202
+ end
203
+ end
204
+ end
@@ -0,0 +1,207 @@
1
+ begin
2
+ require 'dalli'
3
+ rescue LoadError => e
4
+ $stderr.puts "You don't have dalli installed in your application. Please add it to your Gemfile and run bundle install"
5
+ raise e
6
+ end
7
+
8
+ require 'digest/md5'
9
+ require 'active_support/core_ext/marshal'
10
+ require 'active_support/core_ext/array/extract_options'
11
+
12
+ module ActiveSupport
13
+ module Cache
14
+ # A cache store implementation which stores data in Memcached:
15
+ # http://memcached.org/
16
+ #
17
+ # This is currently the most popular cache store for production websites.
18
+ #
19
+ # Special features:
20
+ # - Clustering and load balancing. One can specify multiple memcached servers,
21
+ # and MemCacheStore will load balance between all available servers. If a
22
+ # server goes down, then MemCacheStore will ignore it until it comes back up.
23
+ #
24
+ # MemCacheStore implements the Strategy::LocalCache strategy which implements
25
+ # an in-memory cache inside of a block.
26
+ class MemCacheStore < Store
27
+ # Provide support for raw values in the local cache strategy.
28
+ module LocalCacheWithRaw # :nodoc:
29
+ protected
30
+ def read_entry(key, options)
31
+ entry = super
32
+ if options[:raw] && local_cache && entry
33
+ entry = deserialize_entry(entry.value)
34
+ end
35
+ entry
36
+ end
37
+
38
+ def write_entry(key, entry, options) # :nodoc:
39
+ if options[:raw] && local_cache
40
+ raw_entry = Entry.new(entry.value.to_s)
41
+ raw_entry.expires_at = entry.expires_at
42
+ super(key, raw_entry, options)
43
+ else
44
+ super
45
+ end
46
+ end
47
+ end
48
+
49
+ prepend Strategy::LocalCache
50
+ prepend LocalCacheWithRaw
51
+
52
+ ESCAPE_KEY_CHARS = /[\x00-\x20%\x7F-\xFF]/n
53
+
54
+ # Creates a new Dalli::Client instance with specified addresses and options.
55
+ # By default address is equal localhost:11211.
56
+ #
57
+ # ActiveSupport::Cache::MemCacheStore.build_mem_cache
58
+ # # => #<Dalli::Client:0x007f98a47d2028 @servers=["localhost:11211"], @options={}, @ring=nil>
59
+ # ActiveSupport::Cache::MemCacheStore.build_mem_cache('localhost:10290')
60
+ # # => #<Dalli::Client:0x007f98a47b3a60 @servers=["localhost:10290"], @options={}, @ring=nil>
61
+ def self.build_mem_cache(*addresses) # :nodoc:
62
+ addresses = addresses.flatten
63
+ options = addresses.extract_options!
64
+ addresses = ["localhost:11211"] if addresses.empty?
65
+ Dalli::Client.new(addresses, options)
66
+ end
67
+
68
+ # Creates a new MemCacheStore object, with the given memcached server
69
+ # addresses. Each address is either a host name, or a host-with-port string
70
+ # in the form of "host_name:port". For example:
71
+ #
72
+ # ActiveSupport::Cache::MemCacheStore.new("localhost", "server-downstairs.localnetwork:8229")
73
+ #
74
+ # If no addresses are specified, then MemCacheStore will connect to
75
+ # localhost port 11211 (the default memcached port).
76
+ def initialize(*addresses)
77
+ addresses = addresses.flatten
78
+ options = addresses.extract_options!
79
+ super(options)
80
+
81
+ unless [String, Dalli::Client, NilClass].include?(addresses.first.class)
82
+ raise ArgumentError, "First argument must be an empty array, an array of hosts or a Dalli::Client instance."
83
+ end
84
+ if addresses.first.is_a?(Dalli::Client)
85
+ @data = addresses.first
86
+ else
87
+ mem_cache_options = options.dup
88
+ UNIVERSAL_OPTIONS.each{|name| mem_cache_options.delete(name)}
89
+ @data = self.class.build_mem_cache(*(addresses + [mem_cache_options]))
90
+ end
91
+ end
92
+
93
+ # Reads multiple values from the cache using a single call to the
94
+ # servers for all keys. Options can be passed in the last argument.
95
+ def read_multi(*names)
96
+ options = names.extract_options!
97
+ options = merged_options(options)
98
+
99
+ keys_to_names = Hash[names.map{|name| [normalize_key(name, options), name]}]
100
+ raw_values = @data.get_multi(keys_to_names.keys)
101
+ values = {}
102
+ raw_values.each do |key, value|
103
+ entry = deserialize_entry(value)
104
+ values[keys_to_names[key]] = entry.value unless entry.expired?
105
+ end
106
+ values
107
+ end
108
+
109
+ # Increment a cached value. This method uses the memcached incr atomic
110
+ # operator and can only be used on values written with the :raw option.
111
+ # Calling it on a value not stored with :raw will initialize that value
112
+ # to zero.
113
+ def increment(name, amount = 1, options = nil) # :nodoc:
114
+ options = merged_options(options)
115
+ instrument(:increment, name, :amount => amount) do
116
+ rescue_error_with nil do
117
+ @data.incr(normalize_key(name, options), amount)
118
+ end
119
+ end
120
+ end
121
+
122
+ # Decrement a cached value. This method uses the memcached decr atomic
123
+ # operator and can only be used on values written with the :raw option.
124
+ # Calling it on a value not stored with :raw will initialize that value
125
+ # to zero.
126
+ def decrement(name, amount = 1, options = nil) # :nodoc:
127
+ options = merged_options(options)
128
+ instrument(:decrement, name, :amount => amount) do
129
+ rescue_error_with nil do
130
+ @data.decr(normalize_key(name, options), amount)
131
+ end
132
+ end
133
+ end
134
+
135
+ # Clear the entire cache on all memcached servers. This method should
136
+ # be used with care when shared cache is being used.
137
+ def clear(options = nil)
138
+ rescue_error_with(nil) { @data.flush_all }
139
+ end
140
+
141
+ # Get the statistics from the memcached servers.
142
+ def stats
143
+ @data.stats
144
+ end
145
+
146
+ protected
147
+ # Read an entry from the cache.
148
+ def read_entry(key, options) # :nodoc:
149
+ rescue_error_with(nil) { deserialize_entry(@data.get(key, options)) }
150
+ end
151
+
152
+ # Write an entry to the cache.
153
+ def write_entry(key, entry, options) # :nodoc:
154
+ method = options && options[:unless_exist] ? :add : :set
155
+ value = options[:raw] ? entry.value.to_s : entry
156
+ expires_in = options[:expires_in].to_i
157
+ if expires_in > 0 && !options[:raw]
158
+ # Set the memcache expire a few minutes in the future to support race condition ttls on read
159
+ expires_in += 5.minutes
160
+ end
161
+ rescue_error_with false do
162
+ @data.send(method, key, value, expires_in, options)
163
+ end
164
+ end
165
+
166
+ # Delete an entry from the cache.
167
+ def delete_entry(key, options) # :nodoc:
168
+ rescue_error_with(false) { @data.delete(key) }
169
+ end
170
+
171
+ private
172
+
173
+ # Memcache keys are binaries. So we need to force their encoding to binary
174
+ # before applying the regular expression to ensure we are escaping all
175
+ # characters properly.
176
+ def normalize_key(key, options)
177
+ key = super.dup
178
+ key = key.force_encoding(Encoding::ASCII_8BIT)
179
+ key = key.gsub(ESCAPE_KEY_CHARS){ |match| "%#{match.getbyte(0).to_s(16).upcase}" }
180
+ key = "#{key[0, 213]}:md5:#{Digest::MD5.hexdigest(key)}" if key.size > 250
181
+ key
182
+ end
183
+
184
+ def escape_key(key)
185
+ ActiveSupport::Deprecation.warn(<<-MESSAGE.strip_heredoc)
186
+ `escape_key` is deprecated and will be removed from Rails 5.1.
187
+ Please use `normalize_key` which will return a fully resolved key or nothing.
188
+ MESSAGE
189
+ key
190
+ end
191
+
192
+ def deserialize_entry(raw_value)
193
+ if raw_value
194
+ entry = Marshal.load(raw_value) rescue raw_value
195
+ entry.is_a?(Entry) ? entry : Entry.new(entry)
196
+ end
197
+ end
198
+
199
+ def rescue_error_with(fallback)
200
+ yield
201
+ rescue Dalli::DalliError => e
202
+ logger.error("DalliError (#{e}): #{e.message}") if logger
203
+ fallback
204
+ end
205
+ end
206
+ end
207
+ end
@@ -0,0 +1,167 @@
1
+ require 'monitor'
2
+
3
+ module ActiveSupport
4
+ module Cache
5
+ # A cache store implementation which stores everything into memory in the
6
+ # same process. If you're running multiple Ruby on Rails server processes
7
+ # (which is the case if you're using mongrel_cluster or Phusion Passenger),
8
+ # then this means that Rails server process instances won't be able
9
+ # to share cache data with each other and this may not be the most
10
+ # appropriate cache in that scenario.
11
+ #
12
+ # This cache has a bounded size specified by the :size options to the
13
+ # initializer (default is 32Mb). When the cache exceeds the allotted size,
14
+ # a cleanup will occur which tries to prune the cache down to three quarters
15
+ # of the maximum size by removing the least recently used entries.
16
+ #
17
+ # MemoryStore is thread-safe.
18
+ class MemoryStore < Store
19
+ def initialize(options = nil)
20
+ options ||= {}
21
+ super(options)
22
+ @data = {}
23
+ @key_access = {}
24
+ @max_size = options[:size] || 32.megabytes
25
+ @max_prune_time = options[:max_prune_time] || 2
26
+ @cache_size = 0
27
+ @monitor = Monitor.new
28
+ @pruning = false
29
+ end
30
+
31
+ def clear(options = nil)
32
+ synchronize do
33
+ @data.clear
34
+ @key_access.clear
35
+ @cache_size = 0
36
+ end
37
+ end
38
+
39
+ # Preemptively iterates through all stored keys and removes the ones which have expired.
40
+ def cleanup(options = nil)
41
+ options = merged_options(options)
42
+ instrument(:cleanup, :size => @data.size) do
43
+ keys = synchronize{ @data.keys }
44
+ keys.each do |key|
45
+ entry = @data[key]
46
+ delete_entry(key, options) if entry && entry.expired?
47
+ end
48
+ end
49
+ end
50
+
51
+ # To ensure entries fit within the specified memory prune the cache by removing the least
52
+ # recently accessed entries.
53
+ def prune(target_size, max_time = nil)
54
+ return if pruning?
55
+ @pruning = true
56
+ begin
57
+ start_time = Time.now
58
+ cleanup
59
+ instrument(:prune, target_size, :from => @cache_size) do
60
+ keys = synchronize{ @key_access.keys.sort{|a,b| @key_access[a].to_f <=> @key_access[b].to_f} }
61
+ keys.each do |key|
62
+ delete_entry(key, options)
63
+ return if @cache_size <= target_size || (max_time && Time.now - start_time > max_time)
64
+ end
65
+ end
66
+ ensure
67
+ @pruning = false
68
+ end
69
+ end
70
+
71
+ # Returns true if the cache is currently being pruned.
72
+ def pruning?
73
+ @pruning
74
+ end
75
+
76
+ # Increment an integer value in the cache.
77
+ def increment(name, amount = 1, options = nil)
78
+ modify_value(name, amount, options)
79
+ end
80
+
81
+ # Decrement an integer value in the cache.
82
+ def decrement(name, amount = 1, options = nil)
83
+ modify_value(name, -amount, options)
84
+ end
85
+
86
+ def delete_matched(matcher, options = nil)
87
+ options = merged_options(options)
88
+ instrument(:delete_matched, matcher.inspect) do
89
+ matcher = key_matcher(matcher, options)
90
+ keys = synchronize { @data.keys }
91
+ keys.each do |key|
92
+ delete_entry(key, options) if key.match(matcher)
93
+ end
94
+ end
95
+ end
96
+
97
+ def inspect # :nodoc:
98
+ "<##{self.class.name} entries=#{@data.size}, size=#{@cache_size}, options=#{@options.inspect}>"
99
+ end
100
+
101
+ # Synchronize calls to the cache. This should be called wherever the underlying cache implementation
102
+ # is not thread safe.
103
+ def synchronize(&block) # :nodoc:
104
+ @monitor.synchronize(&block)
105
+ end
106
+
107
+ protected
108
+
109
+ PER_ENTRY_OVERHEAD = 240
110
+
111
+ def cached_size(key, entry) # :nodoc:
112
+ key.to_s.bytesize + entry.size + PER_ENTRY_OVERHEAD
113
+ end
114
+
115
+ def read_entry(key, options) # :nodoc:
116
+ entry = @data[key]
117
+ synchronize do
118
+ if entry
119
+ @key_access[key] = Time.now.to_f
120
+ else
121
+ @key_access.delete(key)
122
+ end
123
+ end
124
+ entry
125
+ end
126
+
127
+ def write_entry(key, entry, options) # :nodoc:
128
+ entry.dup_value!
129
+ synchronize do
130
+ old_entry = @data[key]
131
+ return false if @data.key?(key) && options[:unless_exist]
132
+ if old_entry
133
+ @cache_size -= (old_entry.size - entry.size)
134
+ else
135
+ @cache_size += cached_size(key, entry)
136
+ end
137
+ @key_access[key] = Time.now.to_f
138
+ @data[key] = entry
139
+ prune(@max_size * 0.75, @max_prune_time) if @cache_size > @max_size
140
+ true
141
+ end
142
+ end
143
+
144
+ def delete_entry(key, options) # :nodoc:
145
+ synchronize do
146
+ @key_access.delete(key)
147
+ entry = @data.delete(key)
148
+ @cache_size -= cached_size(key, entry) if entry
149
+ !!entry
150
+ end
151
+ end
152
+
153
+ private
154
+
155
+ def modify_value(name, amount, options)
156
+ synchronize do
157
+ options = merged_options(options)
158
+ if num = read(name, options)
159
+ num = num.to_i + amount
160
+ write(name, num, options)
161
+ num
162
+ end
163
+ end
164
+ end
165
+ end
166
+ end
167
+ end