activesupport 6.0.0

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