activesupport 5.2.4.3 → 7.0.3

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 (228) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +244 -459
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +4 -3
  5. data/lib/active_support/actionable_error.rb +48 -0
  6. data/lib/active_support/array_inquirer.rb +2 -2
  7. data/lib/active_support/backtrace_cleaner.rb +31 -5
  8. data/lib/active_support/benchmarkable.rb +3 -3
  9. data/lib/active_support/cache/file_store.rb +47 -41
  10. data/lib/active_support/cache/mem_cache_store.rb +151 -40
  11. data/lib/active_support/cache/memory_store.rb +68 -34
  12. data/lib/active_support/cache/null_store.rb +16 -3
  13. data/lib/active_support/cache/redis_cache_store.rb +103 -101
  14. data/lib/active_support/cache/strategy/local_cache.rb +56 -64
  15. data/lib/active_support/cache.rb +333 -116
  16. data/lib/active_support/callbacks.rb +244 -128
  17. data/lib/active_support/code_generator.rb +65 -0
  18. data/lib/active_support/concern.rb +72 -5
  19. data/lib/active_support/concurrency/load_interlock_aware_monitor.rb +16 -0
  20. data/lib/active_support/concurrency/share_lock.rb +2 -3
  21. data/lib/active_support/configurable.rb +15 -16
  22. data/lib/active_support/configuration_file.rb +51 -0
  23. data/lib/active_support/core_ext/array/access.rb +15 -7
  24. data/lib/active_support/core_ext/array/conversions.rb +18 -17
  25. data/lib/active_support/core_ext/array/deprecated_conversions.rb +25 -0
  26. data/lib/active_support/core_ext/array/extract.rb +21 -0
  27. data/lib/active_support/core_ext/array/grouping.rb +6 -6
  28. data/lib/active_support/core_ext/array/inquiry.rb +2 -2
  29. data/lib/active_support/core_ext/array.rb +2 -1
  30. data/lib/active_support/core_ext/benchmark.rb +2 -2
  31. data/lib/active_support/core_ext/big_decimal/conversions.rb +1 -1
  32. data/lib/active_support/core_ext/class/attribute.rb +32 -47
  33. data/lib/active_support/core_ext/class/subclasses.rb +9 -22
  34. data/lib/active_support/core_ext/date/blank.rb +1 -1
  35. data/lib/active_support/core_ext/date/calculations.rb +15 -14
  36. data/lib/active_support/core_ext/date/conversions.rb +16 -15
  37. data/lib/active_support/core_ext/date/deprecated_conversions.rb +26 -0
  38. data/lib/active_support/core_ext/date.rb +1 -0
  39. data/lib/active_support/core_ext/date_and_time/calculations.rb +41 -51
  40. data/lib/active_support/core_ext/date_and_time/compatibility.rb +15 -0
  41. data/lib/active_support/core_ext/date_and_time/zones.rb +0 -1
  42. data/lib/active_support/core_ext/date_time/blank.rb +1 -1
  43. data/lib/active_support/core_ext/date_time/calculations.rb +1 -1
  44. data/lib/active_support/core_ext/date_time/conversions.rb +13 -14
  45. data/lib/active_support/core_ext/date_time/deprecated_conversions.rb +22 -0
  46. data/lib/active_support/core_ext/date_time.rb +1 -0
  47. data/lib/active_support/core_ext/digest/uuid.rb +39 -13
  48. data/lib/active_support/core_ext/enumerable.rb +241 -76
  49. data/lib/active_support/core_ext/file/atomic.rb +3 -1
  50. data/lib/active_support/core_ext/hash/conversions.rb +3 -4
  51. data/lib/active_support/core_ext/hash/deep_transform_values.rb +46 -0
  52. data/lib/active_support/core_ext/hash/except.rb +2 -2
  53. data/lib/active_support/core_ext/hash/indifferent_access.rb +3 -3
  54. data/lib/active_support/core_ext/hash/keys.rb +2 -31
  55. data/lib/active_support/core_ext/hash/slice.rb +6 -27
  56. data/lib/active_support/core_ext/hash.rb +1 -2
  57. data/lib/active_support/core_ext/integer/multiple.rb +1 -1
  58. data/lib/active_support/core_ext/kernel/reporting.rb +4 -4
  59. data/lib/active_support/core_ext/kernel/singleton_class.rb +1 -1
  60. data/lib/active_support/core_ext/kernel.rb +0 -1
  61. data/lib/active_support/core_ext/load_error.rb +1 -1
  62. data/lib/active_support/core_ext/module/attr_internal.rb +2 -2
  63. data/lib/active_support/core_ext/module/attribute_accessors.rb +32 -39
  64. data/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb +35 -28
  65. data/lib/active_support/core_ext/module/concerning.rb +8 -2
  66. data/lib/active_support/core_ext/module/delegation.rb +70 -33
  67. data/lib/active_support/core_ext/module/introspection.rb +16 -15
  68. data/lib/active_support/core_ext/module/redefine_method.rb +8 -17
  69. data/lib/active_support/core_ext/module.rb +0 -1
  70. data/lib/active_support/core_ext/name_error.rb +23 -2
  71. data/lib/active_support/core_ext/numeric/conversions.rb +132 -129
  72. data/lib/active_support/core_ext/numeric/deprecated_conversions.rb +60 -0
  73. data/lib/active_support/core_ext/numeric.rb +1 -1
  74. data/lib/active_support/core_ext/object/acts_like.rb +29 -5
  75. data/lib/active_support/core_ext/object/blank.rb +3 -4
  76. data/lib/active_support/core_ext/object/deep_dup.rb +1 -1
  77. data/lib/active_support/core_ext/object/duplicable.rb +14 -110
  78. data/lib/active_support/core_ext/object/json.rb +44 -27
  79. data/lib/active_support/core_ext/object/to_query.rb +2 -2
  80. data/lib/active_support/core_ext/object/try.rb +24 -14
  81. data/lib/active_support/core_ext/object/with_options.rb +21 -2
  82. data/lib/active_support/core_ext/pathname/existence.rb +21 -0
  83. data/lib/active_support/core_ext/pathname.rb +3 -0
  84. data/lib/active_support/core_ext/range/compare_range.rb +23 -27
  85. data/lib/active_support/core_ext/range/conversions.rb +32 -30
  86. data/lib/active_support/core_ext/range/deprecated_conversions.rb +26 -0
  87. data/lib/active_support/core_ext/range/each.rb +1 -2
  88. data/lib/active_support/core_ext/range/include_time_with_zone.rb +4 -20
  89. data/lib/active_support/core_ext/range/overlaps.rb +1 -1
  90. data/lib/active_support/core_ext/range.rb +1 -1
  91. data/lib/active_support/core_ext/regexp.rb +8 -5
  92. data/lib/active_support/core_ext/securerandom.rb +23 -3
  93. data/lib/active_support/core_ext/string/access.rb +5 -16
  94. data/lib/active_support/core_ext/string/conversions.rb +3 -2
  95. data/lib/active_support/core_ext/string/filters.rb +42 -1
  96. data/lib/active_support/core_ext/string/inflections.rb +46 -7
  97. data/lib/active_support/core_ext/string/inquiry.rb +2 -1
  98. data/lib/active_support/core_ext/string/multibyte.rb +6 -5
  99. data/lib/active_support/core_ext/string/output_safety.rb +129 -20
  100. data/lib/active_support/core_ext/string/starts_ends_with.rb +2 -2
  101. data/lib/active_support/core_ext/string/strip.rb +3 -1
  102. data/lib/active_support/core_ext/symbol/starts_ends_with.rb +6 -0
  103. data/lib/active_support/core_ext/symbol.rb +3 -0
  104. data/lib/active_support/core_ext/time/calculations.rb +59 -10
  105. data/lib/active_support/core_ext/time/conversions.rb +15 -12
  106. data/lib/active_support/core_ext/time/deprecated_conversions.rb +22 -0
  107. data/lib/active_support/core_ext/time/zones.rb +7 -22
  108. data/lib/active_support/core_ext/time.rb +1 -0
  109. data/lib/active_support/core_ext/uri.rb +3 -22
  110. data/lib/active_support/core_ext.rb +2 -1
  111. data/lib/active_support/current_attributes/test_helper.rb +13 -0
  112. data/lib/active_support/current_attributes.rb +47 -16
  113. data/lib/active_support/dependencies/interlock.rb +10 -18
  114. data/lib/active_support/dependencies/require_dependency.rb +28 -0
  115. data/lib/active_support/dependencies.rb +60 -715
  116. data/lib/active_support/deprecation/behaviors.rb +21 -5
  117. data/lib/active_support/deprecation/disallowed.rb +56 -0
  118. data/lib/active_support/deprecation/instance_delegator.rb +0 -1
  119. data/lib/active_support/deprecation/method_wrappers.rb +18 -23
  120. data/lib/active_support/deprecation/proxy_wrappers.rb +31 -8
  121. data/lib/active_support/deprecation/reporting.rb +50 -7
  122. data/lib/active_support/deprecation.rb +7 -2
  123. data/lib/active_support/descendants_tracker.rb +190 -34
  124. data/lib/active_support/digest.rb +5 -3
  125. data/lib/active_support/duration/iso8601_parser.rb +5 -7
  126. data/lib/active_support/duration/iso8601_serializer.rb +27 -15
  127. data/lib/active_support/duration.rb +149 -67
  128. data/lib/active_support/encrypted_configuration.rb +12 -5
  129. data/lib/active_support/encrypted_file.rb +23 -5
  130. data/lib/active_support/environment_inquirer.rb +20 -0
  131. data/lib/active_support/error_reporter.rb +117 -0
  132. data/lib/active_support/evented_file_update_checker.rb +85 -122
  133. data/lib/active_support/execution_context/test_helper.rb +13 -0
  134. data/lib/active_support/execution_context.rb +53 -0
  135. data/lib/active_support/execution_wrapper.rb +44 -21
  136. data/lib/active_support/executor/test_helper.rb +7 -0
  137. data/lib/active_support/file_update_checker.rb +0 -1
  138. data/lib/active_support/fork_tracker.rb +71 -0
  139. data/lib/active_support/gem_version.rb +5 -5
  140. data/lib/active_support/hash_with_indifferent_access.rb +73 -43
  141. data/lib/active_support/html_safe_translation.rb +43 -0
  142. data/lib/active_support/i18n.rb +2 -0
  143. data/lib/active_support/i18n_railtie.rb +15 -8
  144. data/lib/active_support/inflector/inflections.rb +25 -14
  145. data/lib/active_support/inflector/methods.rb +38 -71
  146. data/lib/active_support/inflector/transliterate.rb +47 -18
  147. data/lib/active_support/isolated_execution_state.rb +72 -0
  148. data/lib/active_support/json/decoding.rb +25 -26
  149. data/lib/active_support/json/encoding.rb +14 -6
  150. data/lib/active_support/key_generator.rb +23 -38
  151. data/lib/active_support/lazy_load_hooks.rb +19 -5
  152. data/lib/active_support/locale/en.rb +33 -0
  153. data/lib/active_support/locale/en.yml +8 -4
  154. data/lib/active_support/log_subscriber/test_helper.rb +2 -2
  155. data/lib/active_support/log_subscriber.rb +51 -11
  156. data/lib/active_support/logger.rb +6 -22
  157. data/lib/active_support/logger_silence.rb +11 -19
  158. data/lib/active_support/logger_thread_safe_level.rb +45 -10
  159. data/lib/active_support/message_encryptor.rb +20 -19
  160. data/lib/active_support/message_verifier.rb +53 -21
  161. data/lib/active_support/messages/metadata.rb +13 -4
  162. data/lib/active_support/messages/rotation_configuration.rb +2 -1
  163. data/lib/active_support/messages/rotator.rb +10 -9
  164. data/lib/active_support/multibyte/chars.rb +17 -76
  165. data/lib/active_support/multibyte/unicode.rb +7 -331
  166. data/lib/active_support/multibyte.rb +1 -1
  167. data/lib/active_support/notifications/fanout.rb +163 -37
  168. data/lib/active_support/notifications/instrumenter.rb +90 -11
  169. data/lib/active_support/notifications.rb +88 -30
  170. data/lib/active_support/number_helper/number_converter.rb +6 -9
  171. data/lib/active_support/number_helper/number_to_currency_converter.rb +12 -12
  172. data/lib/active_support/number_helper/number_to_delimited_converter.rb +4 -3
  173. data/lib/active_support/number_helper/number_to_human_converter.rb +4 -3
  174. data/lib/active_support/number_helper/number_to_human_size_converter.rb +5 -4
  175. data/lib/active_support/number_helper/number_to_percentage_converter.rb +3 -1
  176. data/lib/active_support/number_helper/number_to_phone_converter.rb +3 -2
  177. data/lib/active_support/number_helper/number_to_rounded_converter.rb +12 -7
  178. data/lib/active_support/number_helper/rounding_helper.rb +12 -32
  179. data/lib/active_support/number_helper.rb +36 -12
  180. data/lib/active_support/option_merger.rb +15 -4
  181. data/lib/active_support/ordered_hash.rb +2 -2
  182. data/lib/active_support/ordered_options.rb +14 -4
  183. data/lib/active_support/parameter_filter.rb +138 -0
  184. data/lib/active_support/per_thread_registry.rb +6 -1
  185. data/lib/active_support/rails.rb +1 -10
  186. data/lib/active_support/railtie.rb +77 -5
  187. data/lib/active_support/reloader.rb +5 -6
  188. data/lib/active_support/rescuable.rb +8 -8
  189. data/lib/active_support/ruby_features.rb +7 -0
  190. data/lib/active_support/secure_compare_rotator.rb +51 -0
  191. data/lib/active_support/security_utils.rb +19 -12
  192. data/lib/active_support/string_inquirer.rb +2 -3
  193. data/lib/active_support/subscriber.rb +79 -46
  194. data/lib/active_support/tagged_logging.rb +58 -9
  195. data/lib/active_support/test_case.rb +79 -0
  196. data/lib/active_support/testing/assertions.rb +62 -11
  197. data/lib/active_support/testing/deprecation.rb +52 -2
  198. data/lib/active_support/testing/file_fixtures.rb +2 -0
  199. data/lib/active_support/testing/isolation.rb +4 -4
  200. data/lib/active_support/testing/method_call_assertions.rb +32 -5
  201. data/lib/active_support/testing/parallelization/server.rb +82 -0
  202. data/lib/active_support/testing/parallelization/worker.rb +103 -0
  203. data/lib/active_support/testing/parallelization.rb +55 -0
  204. data/lib/active_support/testing/parallelize_executor.rb +76 -0
  205. data/lib/active_support/testing/stream.rb +4 -7
  206. data/lib/active_support/testing/tagged_logging.rb +1 -1
  207. data/lib/active_support/testing/time_helpers.rb +60 -14
  208. data/lib/active_support/time_with_zone.rb +139 -64
  209. data/lib/active_support/values/time_zone.rb +66 -30
  210. data/lib/active_support/version.rb +1 -1
  211. data/lib/active_support/xml_mini/jdom.rb +3 -4
  212. data/lib/active_support/xml_mini/libxml.rb +7 -7
  213. data/lib/active_support/xml_mini/libxmlsax.rb +5 -5
  214. data/lib/active_support/xml_mini/nokogiri.rb +6 -6
  215. data/lib/active_support/xml_mini/nokogirisax.rb +4 -4
  216. data/lib/active_support/xml_mini/rexml.rb +11 -4
  217. data/lib/active_support/xml_mini.rb +7 -14
  218. data/lib/active_support.rb +30 -1
  219. metadata +64 -35
  220. data/lib/active_support/core_ext/array/prepend_and_append.rb +0 -9
  221. data/lib/active_support/core_ext/hash/compact.rb +0 -29
  222. data/lib/active_support/core_ext/hash/transform_values.rb +0 -32
  223. data/lib/active_support/core_ext/kernel/agnostics.rb +0 -13
  224. data/lib/active_support/core_ext/marshal.rb +0 -24
  225. data/lib/active_support/core_ext/module/reachable.rb +0 -11
  226. data/lib/active_support/core_ext/numeric/inquiry.rb +0 -28
  227. data/lib/active_support/core_ext/range/include_range.rb +0 -3
  228. data/lib/active_support/values/unicode_tables.dat +0 -0
data/MIT-LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2005-2018 David Heinemeier Hansson
1
+ Copyright (c) 2005-2022 David Heinemeier Hansson
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining
4
4
  a copy of this software and associated documentation files (the
data/README.rdoc CHANGED
@@ -5,6 +5,7 @@ extensions that were found useful for the Rails framework. These additions
5
5
  reside in this package so they can be loaded as needed in Ruby projects
6
6
  outside of Rails.
7
7
 
8
+ You can read more about the extensions in the {Active Support Core Extensions}[https://edgeguides.rubyonrails.org/active_support_core_extensions.html] guide.
8
9
 
9
10
  == Download and installation
10
11
 
@@ -14,7 +15,7 @@ The latest version of Active Support can be installed with RubyGems:
14
15
 
15
16
  Source code can be downloaded as part of the Rails project on GitHub:
16
17
 
17
- * https://github.com/rails/rails/tree/5-2-stable/activesupport
18
+ * https://github.com/rails/rails/tree/main/activesupport
18
19
 
19
20
 
20
21
  == License
@@ -28,7 +29,7 @@ Active Support is released under the MIT license:
28
29
 
29
30
  API documentation is at:
30
31
 
31
- * http://api.rubyonrails.org
32
+ * https://api.rubyonrails.org
32
33
 
33
34
  Bug reports for the Ruby on Rails project can be filed here:
34
35
 
@@ -36,4 +37,4 @@ Bug reports for the Ruby on Rails project can be filed here:
36
37
 
37
38
  Feature requests should be discussed on the rails-core mailing list here:
38
39
 
39
- * https://groups.google.com/forum/?fromgroups#!forum/rubyonrails-core
40
+ * https://discuss.rubyonrails.org/c/rubyonrails-core
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveSupport
4
+ # Actionable errors lets you define actions to resolve an error.
5
+ #
6
+ # To make an error actionable, include the <tt>ActiveSupport::ActionableError</tt>
7
+ # module and invoke the +action+ class macro to define the action. An action
8
+ # needs a name and a block to execute.
9
+ module ActionableError
10
+ extend Concern
11
+
12
+ class NonActionable < StandardError; end
13
+
14
+ included do
15
+ class_attribute :_actions, default: {}
16
+ end
17
+
18
+ def self.actions(error) # :nodoc:
19
+ case error
20
+ when ActionableError, -> it { Class === it && it < ActionableError }
21
+ error._actions
22
+ else
23
+ {}
24
+ end
25
+ end
26
+
27
+ def self.dispatch(error, name) # :nodoc:
28
+ actions(error).fetch(name).call
29
+ rescue KeyError
30
+ raise NonActionable, "Cannot find action \"#{name}\""
31
+ end
32
+
33
+ module ClassMethods
34
+ # Defines an action that can resolve the error.
35
+ #
36
+ # class PendingMigrationError < MigrationError
37
+ # include ActiveSupport::ActionableError
38
+ #
39
+ # action "Run pending migrations" do
40
+ # ActiveRecord::Tasks::DatabaseTasks.migrate
41
+ # end
42
+ # end
43
+ def action(name, &block)
44
+ _actions[name] = block
45
+ end
46
+ end
47
+ end
48
+ end
@@ -34,11 +34,11 @@ module ActiveSupport
34
34
 
35
35
  private
36
36
  def respond_to_missing?(name, include_private = false)
37
- (name[-1] == "?") || super
37
+ name.end_with?("?") || super
38
38
  end
39
39
 
40
40
  def method_missing(name, *args)
41
- if name[-1] == "?"
41
+ if name.end_with?("?")
42
42
  any?(name[0..-2])
43
43
  else
44
44
  super
@@ -16,21 +16,24 @@ module ActiveSupport
16
16
  #
17
17
  # bc = ActiveSupport::BacktraceCleaner.new
18
18
  # bc.add_filter { |line| line.gsub(Rails.root.to_s, '') } # strip the Rails.root prefix
19
- # bc.add_silencer { |line| line =~ /puma|rubygems/ } # skip any lines from puma or rubygems
19
+ # bc.add_silencer { |line| /puma|rubygems/.match?(line) } # skip any lines from puma or rubygems
20
20
  # bc.clean(exception.backtrace) # perform the cleanup
21
21
  #
22
22
  # To reconfigure an existing BacktraceCleaner (like the default one in Rails)
23
23
  # and show as much data as possible, you can always call
24
- # <tt>BacktraceCleaner#remove_silencers!</tt>, which will restore the
24
+ # BacktraceCleaner#remove_silencers!, which will restore the
25
25
  # backtrace to a pristine state. If you need to reconfigure an existing
26
26
  # BacktraceCleaner so that it does not filter or modify the paths of any lines
27
- # of the backtrace, you can call <tt>BacktraceCleaner#remove_filters!</tt>
27
+ # of the backtrace, you can call BacktraceCleaner#remove_filters!
28
28
  # These two methods will give you a completely untouched backtrace.
29
29
  #
30
30
  # Inspired by the Quiet Backtrace gem by thoughtbot.
31
31
  class BacktraceCleaner
32
32
  def initialize
33
33
  @filters, @silencers = [], []
34
+ add_gem_filter
35
+ add_gem_silencer
36
+ add_stdlib_silencer
34
37
  end
35
38
 
36
39
  # Returns the backtrace after all filters and silencers have been run
@@ -62,7 +65,7 @@ module ActiveSupport
62
65
  # for a given line, it will be excluded from the clean backtrace.
63
66
  #
64
67
  # # Will reject all lines that include the word "puma", like "/gems/puma/server.rb" or "/app/my_puma_server/rb"
65
- # backtrace_cleaner.add_silencer { |line| line =~ /puma/ }
68
+ # backtrace_cleaner.add_silencer { |line| /puma/.match?(line) }
66
69
  def add_silencer(&block)
67
70
  @silencers << block
68
71
  end
@@ -82,6 +85,25 @@ module ActiveSupport
82
85
  end
83
86
 
84
87
  private
88
+ FORMATTED_GEMS_PATTERN = /\A[^\/]+ \([\w.]+\) /
89
+
90
+ def add_gem_filter
91
+ gems_paths = (Gem.path | [Gem.default_dir]).map { |p| Regexp.escape(p) }
92
+ return if gems_paths.empty?
93
+
94
+ gems_regexp = %r{\A(#{gems_paths.join('|')})/(bundler/)?gems/([^/]+)-([\w.]+)/(.*)}
95
+ gems_result = '\3 (\4) \5'
96
+ add_filter { |line| line.sub(gems_regexp, gems_result) }
97
+ end
98
+
99
+ def add_gem_silencer
100
+ add_silencer { |line| FORMATTED_GEMS_PATTERN.match?(line) }
101
+ end
102
+
103
+ def add_stdlib_silencer
104
+ add_silencer { |line| line.start_with?(RbConfig::CONFIG["rubylibdir"]) }
105
+ end
106
+
85
107
  def filter_backtrace(backtrace)
86
108
  @filters.each do |f|
87
109
  backtrace = backtrace.map { |line| f.call(line) }
@@ -99,7 +121,11 @@ module ActiveSupport
99
121
  end
100
122
 
101
123
  def noise(backtrace)
102
- backtrace - silence(backtrace)
124
+ backtrace.select do |line|
125
+ @silencers.any? do |s|
126
+ s.call(line)
127
+ end
128
+ end
103
129
  end
104
130
  end
105
131
  end
@@ -34,14 +34,14 @@ module ActiveSupport
34
34
  # <% benchmark 'Process data files', level: :info, silence: true do %>
35
35
  # <%= expensive_and_chatty_files_operation %>
36
36
  # <% end %>
37
- def benchmark(message = "Benchmarking", options = {})
37
+ def benchmark(message = "Benchmarking", options = {}, &block)
38
38
  if logger
39
39
  options.assert_valid_keys(:level, :silence)
40
40
  options[:level] ||= :info
41
41
 
42
42
  result = nil
43
- ms = Benchmark.ms { result = options[:silence] ? logger.silence { yield } : yield }
44
- logger.send(options[:level], "%s (%.1fms)" % [ message, ms ])
43
+ ms = Benchmark.ms { result = options[:silence] ? logger.silence(&block) : yield }
44
+ logger.public_send(options[:level], "%s (%.1fms)" % [ message, ms ])
45
45
  result
46
46
  else
47
47
  yield
@@ -1,6 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "active_support/core_ext/marshal"
4
3
  require "active_support/core_ext/file/atomic"
5
4
  require "active_support/core_ext/string/conversions"
6
5
  require "uri/common"
@@ -12,35 +11,38 @@ module ActiveSupport
12
11
  # FileStore implements the Strategy::LocalCache strategy which implements
13
12
  # an in-memory cache inside of a block.
14
13
  class FileStore < Store
15
- prepend Strategy::LocalCache
16
14
  attr_reader :cache_path
17
15
 
18
16
  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)
17
+ FILENAME_MAX_SIZE = 226 # max filename size on file system is 255, minus room for timestamp, pid, and random characters appended by Tempfile (used by atomic write)
20
18
  FILEPATH_MAX_SIZE = 900 # max is 1024, plus some room
21
- EXCLUDED_DIRS = [".", ".."].freeze
22
19
  GITKEEP_FILES = [".gitkeep", ".keep"].freeze
23
20
 
24
- def initialize(cache_path, options = nil)
21
+ def initialize(cache_path, **options)
25
22
  super(options)
26
23
  @cache_path = cache_path.to_s
27
24
  end
28
25
 
26
+ # Advertise cache versioning support.
27
+ def self.supports_cache_versioning?
28
+ true
29
+ end
30
+
29
31
  # Deletes all items from the cache. In this case it deletes all the entries in the specified
30
32
  # file store directory except for .keep or .gitkeep. Be careful which directory is specified in your
31
33
  # config file when using +FileStore+ because everything in that directory will be deleted.
32
34
  def clear(options = nil)
33
- root_dirs = exclude_from(cache_path, EXCLUDED_DIRS + GITKEEP_FILES)
35
+ root_dirs = (Dir.children(cache_path) - GITKEEP_FILES)
34
36
  FileUtils.rm_r(root_dirs.collect { |f| File.join(cache_path, f) })
35
- rescue Errno::ENOENT
37
+ rescue Errno::ENOENT, Errno::ENOTEMPTY
36
38
  end
37
39
 
38
40
  # Preemptively iterates through all stored keys and removes the ones which have expired.
39
41
  def cleanup(options = nil)
40
42
  options = merged_options(options)
41
43
  search_dir(cache_path) do |fname|
42
- entry = read_entry(fname, options)
43
- delete_entry(fname, options) if entry && entry.expired?
44
+ entry = read_entry(fname, **options)
45
+ delete_entry(fname, **options) if entry && entry.expired?
44
46
  end
45
47
  end
46
48
 
@@ -62,38 +64,46 @@ module ActiveSupport
62
64
  matcher = key_matcher(matcher, options)
63
65
  search_dir(cache_path) do |path|
64
66
  key = file_path_key(path)
65
- delete_entry(path, options) if key.match(matcher)
67
+ delete_entry(path, **options) if key.match(matcher)
66
68
  end
67
69
  end
68
70
  end
69
71
 
70
72
  private
71
-
72
- def read_entry(key, options)
73
- if File.exist?(key)
74
- File.open(key) { |f| Marshal.load(f) }
73
+ def read_entry(key, **options)
74
+ if payload = read_serialized_entry(key, **options)
75
+ entry = deserialize_entry(payload)
76
+ entry if entry.is_a?(Cache::Entry)
75
77
  end
76
- rescue => e
77
- logger.error("FileStoreError (#{e}): #{e.message}") if logger
78
+ end
79
+
80
+ def read_serialized_entry(key, **)
81
+ File.binread(key) if File.exist?(key)
82
+ rescue => error
83
+ logger.error("FileStoreError (#{error}): #{error.message}") if logger
78
84
  nil
79
85
  end
80
86
 
81
- def write_entry(key, entry, options)
87
+ def write_entry(key, entry, **options)
88
+ write_serialized_entry(key, serialize_entry(entry, **options), **options)
89
+ end
90
+
91
+ def write_serialized_entry(key, payload, **options)
82
92
  return false if options[:unless_exist] && File.exist?(key)
83
93
  ensure_cache_path(File.dirname(key))
84
- File.atomic_write(key, cache_path) { |f| Marshal.dump(entry, f) }
94
+ File.atomic_write(key, cache_path) { |f| f.write(payload) }
85
95
  true
86
96
  end
87
97
 
88
- def delete_entry(key, options)
98
+ def delete_entry(key, **options)
89
99
  if File.exist?(key)
90
100
  begin
91
101
  File.delete(key)
92
102
  delete_empty_directories(File.dirname(key))
93
103
  true
94
- rescue => e
104
+ rescue
95
105
  # Just in case the error was caused by another process deleting the file first.
96
- raise e if File.exist?(key)
106
+ raise if File.exist?(key)
97
107
  false
98
108
  end
99
109
  end
@@ -103,12 +113,10 @@ module ActiveSupport
103
113
  def lock_file(file_name, &block)
104
114
  if File.exist?(file_name)
105
115
  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
116
+ f.flock File::LOCK_EX
117
+ yield
118
+ ensure
119
+ f.flock File::LOCK_UN
112
120
  end
113
121
  else
114
122
  yield
@@ -127,15 +135,19 @@ module ActiveSupport
127
135
  hash = Zlib.adler32(fname)
128
136
  hash, dir_1 = hash.divmod(0x1000)
129
137
  dir_2 = hash.modulo(0x1000)
130
- fname_paths = []
131
138
 
132
139
  # 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?
140
+ if fname.length < FILENAME_MAX_SIZE
141
+ fname_paths = fname
142
+ else
143
+ fname_paths = []
144
+ begin
145
+ fname_paths << fname[0, FILENAME_MAX_SIZE]
146
+ fname = fname[FILENAME_MAX_SIZE..-1]
147
+ end until fname.blank?
148
+ end
137
149
 
138
- File.join(cache_path, DIR_FORMATTER % dir_1, DIR_FORMATTER % dir_2, *fname_paths)
150
+ File.join(cache_path, DIR_FORMATTER % dir_1, DIR_FORMATTER % dir_2, fname_paths)
139
151
  end
140
152
 
141
153
  # Translate a file path into a key.
@@ -147,7 +159,7 @@ module ActiveSupport
147
159
  # Delete empty directories in the cache.
148
160
  def delete_empty_directories(dir)
149
161
  return if File.realpath(dir) == File.realpath(cache_path)
150
- if exclude_from(dir, EXCLUDED_DIRS).empty?
162
+ if Dir.children(dir).empty?
151
163
  Dir.delete(dir) rescue nil
152
164
  delete_empty_directories(File.dirname(dir))
153
165
  end
@@ -160,8 +172,7 @@ module ActiveSupport
160
172
 
161
173
  def search_dir(dir, &callback)
162
174
  return if !File.exist?(dir)
163
- Dir.foreach(dir) do |d|
164
- next if EXCLUDED_DIRS.include?(d)
175
+ Dir.each_child(dir) do |d|
165
176
  name = File.join(dir, d)
166
177
  if File.directory?(name)
167
178
  search_dir(name, &callback)
@@ -186,11 +197,6 @@ module ActiveSupport
186
197
  end
187
198
  end
188
199
  end
189
-
190
- # Exclude entries from source directory
191
- def exclude_from(source, excludes)
192
- Dir.entries(source).reject { |f| excludes.include?(f) }
193
- end
194
200
  end
195
201
  end
196
202
  end
@@ -7,6 +7,8 @@ rescue LoadError => e
7
7
  raise e
8
8
  end
9
9
 
10
+ require "delegate"
11
+ require "active_support/core_ext/enumerable"
10
12
  require "active_support/core_ext/array/extract_options"
11
13
 
12
14
  module ActiveSupport
@@ -24,36 +26,68 @@ module ActiveSupport
24
26
  # MemCacheStore implements the Strategy::LocalCache strategy which implements
25
27
  # an in-memory cache inside of a block.
26
28
  class MemCacheStore < Store
27
- # Provide support for raw values in the local cache strategy.
28
- module LocalCacheWithRaw # :nodoc:
29
+ # Advertise cache versioning support.
30
+ def self.supports_cache_versioning?
31
+ true
32
+ end
33
+
34
+ prepend Strategy::LocalCache
35
+
36
+ module DupLocalCache
37
+ class DupLocalStore < DelegateClass(Strategy::LocalCache::LocalStore)
38
+ def write_entry(_key, entry)
39
+ if entry.is_a?(Entry)
40
+ entry.dup_value!
41
+ end
42
+ super
43
+ end
44
+
45
+ def fetch_entry(key)
46
+ entry = super do
47
+ new_entry = yield
48
+ if entry.is_a?(Entry)
49
+ new_entry.dup_value!
50
+ end
51
+ new_entry
52
+ end
53
+ entry = entry.dup
54
+
55
+ if entry.is_a?(Entry)
56
+ entry.dup_value!
57
+ end
58
+
59
+ entry
60
+ end
61
+ end
62
+
29
63
  private
30
- def write_entry(key, entry, options)
31
- if options[:raw] && local_cache
32
- raw_entry = Entry.new(entry.value.to_s)
33
- raw_entry.expires_at = entry.expires_at
34
- super(key, raw_entry, options)
64
+ def local_cache
65
+ if ActiveSupport::Cache.format_version == 6.1
66
+ if local_cache = super
67
+ DupLocalStore.new(local_cache)
68
+ end
35
69
  else
36
70
  super
37
71
  end
38
72
  end
39
73
  end
40
-
41
- prepend Strategy::LocalCache
42
- prepend LocalCacheWithRaw
74
+ prepend DupLocalCache
43
75
 
44
76
  ESCAPE_KEY_CHARS = /[\x00-\x20%\x7F-\xFF]/n
45
77
 
46
78
  # Creates a new Dalli::Client instance with specified addresses and options.
47
- # By default address is equal localhost:11211.
79
+ # If no addresses are provided, we give nil to Dalli::Client, so it uses its fallbacks:
80
+ # - ENV["MEMCACHE_SERVERS"] (if defined)
81
+ # - "127.0.0.1:11211" (otherwise)
48
82
  #
49
83
  # ActiveSupport::Cache::MemCacheStore.build_mem_cache
50
- # # => #<Dalli::Client:0x007f98a47d2028 @servers=["localhost:11211"], @options={}, @ring=nil>
84
+ # # => #<Dalli::Client:0x007f98a47d2028 @servers=["127.0.0.1:11211"], @options={}, @ring=nil>
51
85
  # ActiveSupport::Cache::MemCacheStore.build_mem_cache('localhost:10290')
52
86
  # # => #<Dalli::Client:0x007f98a47b3a60 @servers=["localhost:10290"], @options={}, @ring=nil>
53
87
  def self.build_mem_cache(*addresses) # :nodoc:
54
88
  addresses = addresses.flatten
55
89
  options = addresses.extract_options!
56
- addresses = ["localhost:11211"] if addresses.empty?
90
+ addresses = nil if addresses.compact.empty?
57
91
  pool_options = retrieve_pool_options(options)
58
92
 
59
93
  if pool_options.empty?
@@ -70,11 +104,14 @@ module ActiveSupport
70
104
  #
71
105
  # ActiveSupport::Cache::MemCacheStore.new("localhost", "server-downstairs.localnetwork:8229")
72
106
  #
73
- # If no addresses are specified, then MemCacheStore will connect to
74
- # localhost port 11211 (the default memcached port).
107
+ # If no addresses are provided, but <tt>ENV['MEMCACHE_SERVERS']</tt> is defined, it will be used instead. Otherwise,
108
+ # MemCacheStore will connect to localhost:11211 (the default memcached port).
75
109
  def initialize(*addresses)
76
110
  addresses = addresses.flatten
77
111
  options = addresses.extract_options!
112
+ if options.key?(:cache_nils)
113
+ options[:skip_nil] = !options.delete(:cache_nils)
114
+ end
78
115
  super(options)
79
116
 
80
117
  unless [String, Dalli::Client, NilClass].include?(addresses.first.class)
@@ -84,14 +121,16 @@ module ActiveSupport
84
121
  @data = addresses.first
85
122
  else
86
123
  mem_cache_options = options.dup
87
- UNIVERSAL_OPTIONS.each { |name| mem_cache_options.delete(name) }
124
+ # The value "compress: false" prevents duplicate compression within Dalli.
125
+ mem_cache_options[:compress] = false
126
+ (UNIVERSAL_OPTIONS - %i(compress)).each { |name| mem_cache_options.delete(name) }
88
127
  @data = self.class.build_mem_cache(*(addresses + [mem_cache_options]))
89
128
  end
90
129
  end
91
130
 
92
131
  # Increment a cached value. This method uses the memcached incr atomic
93
- # operator and can only be used on values written with the :raw option.
94
- # Calling it on a value not stored with :raw will initialize that value
132
+ # operator and can only be used on values written with the +:raw+ option.
133
+ # Calling it on a value not stored with +:raw+ will initialize that value
95
134
  # to zero.
96
135
  def increment(name, amount = 1, options = nil)
97
136
  options = merged_options(options)
@@ -103,8 +142,8 @@ module ActiveSupport
103
142
  end
104
143
 
105
144
  # Decrement a cached value. This method uses the memcached decr atomic
106
- # operator and can only be used on values written with the :raw option.
107
- # Calling it on a value not stored with :raw will initialize that value
145
+ # operator and can only be used on values written with the +:raw+ option.
146
+ # Calling it on a value not stored with +:raw+ will initialize that value
108
147
  # to zero.
109
148
  def decrement(name, amount = 1, options = nil)
110
149
  options = merged_options(options)
@@ -127,34 +166,93 @@ module ActiveSupport
127
166
  end
128
167
 
129
168
  private
169
+ module Coders # :nodoc:
170
+ class << self
171
+ def [](version)
172
+ case version
173
+ when 6.1
174
+ Rails61Coder
175
+ when 7.0
176
+ Rails70Coder
177
+ else
178
+ raise ArgumentError, "Unknown ActiveSupport::Cache.format_version #{Cache.format_version.inspect}"
179
+ end
180
+ end
181
+ end
182
+
183
+ module Loader
184
+ def load(payload)
185
+ if payload.is_a?(Entry)
186
+ payload
187
+ else
188
+ Cache::Coders::Loader.load(payload)
189
+ end
190
+ end
191
+ end
192
+
193
+ module Rails61Coder
194
+ include Loader
195
+ extend self
196
+
197
+ def dump(entry)
198
+ entry
199
+ end
200
+
201
+ def dump_compressed(entry, threshold)
202
+ entry.compressed(threshold)
203
+ end
204
+ end
205
+
206
+ module Rails70Coder
207
+ include Cache::Coders::Rails70Coder
208
+ include Loader
209
+ extend self
210
+ end
211
+ end
212
+
213
+ def default_coder
214
+ Coders[Cache.format_version]
215
+ end
216
+
130
217
  # Read an entry from the cache.
131
- def read_entry(key, options)
132
- rescue_error_with(nil) { deserialize_entry(@data.with { |c| c.get(key, options) }) }
218
+ def read_entry(key, **options)
219
+ deserialize_entry(read_serialized_entry(key, **options), **options)
220
+ end
221
+
222
+ def read_serialized_entry(key, **options)
223
+ rescue_error_with(nil) do
224
+ @data.with { |c| c.get(key, options) }
225
+ end
133
226
  end
134
227
 
135
228
  # Write an entry to the cache.
136
- def write_entry(key, entry, options)
137
- method = options && options[:unless_exist] ? :add : :set
138
- value = options[:raw] ? entry.value.to_s : entry
229
+ def write_entry(key, entry, **options)
230
+ write_serialized_entry(key, serialize_entry(entry, **options), **options)
231
+ end
232
+
233
+ def write_serialized_entry(key, payload, **options)
234
+ method = options[:unless_exist] ? :add : :set
139
235
  expires_in = options[:expires_in].to_i
140
- if expires_in > 0 && !options[:raw]
236
+ if options[:race_condition_ttl] && expires_in > 0 && !options[:raw]
141
237
  # Set the memcache expire a few minutes in the future to support race condition ttls on read
142
238
  expires_in += 5.minutes
143
239
  end
144
240
  rescue_error_with false do
145
- @data.with { |c| c.send(method, key, value, expires_in, options) }
241
+ # Don't pass compress option to Dalli since we are already dealing with compression.
242
+ options.delete(:compress)
243
+ @data.with { |c| c.send(method, key, payload, expires_in, **options) }
146
244
  end
147
245
  end
148
246
 
149
247
  # Reads multiple entries from the cache implementation.
150
- def read_multi_entries(names, options)
151
- keys_to_names = Hash[names.map { |name| [normalize_key(name, options), name] }]
248
+ def read_multi_entries(names, **options)
249
+ keys_to_names = names.index_by { |name| normalize_key(name, options) }
152
250
 
153
251
  raw_values = @data.with { |c| c.get_multi(keys_to_names.keys) }
154
252
  values = {}
155
253
 
156
254
  raw_values.each do |key, value|
157
- entry = deserialize_entry(value)
255
+ entry = deserialize_entry(value, raw: options[:raw])
158
256
 
159
257
  unless entry.expired? || entry.mismatched?(normalize_version(keys_to_names[key], options))
160
258
  values[keys_to_names[key]] = entry.value
@@ -165,31 +263,44 @@ module ActiveSupport
165
263
  end
166
264
 
167
265
  # Delete an entry from the cache.
168
- def delete_entry(key, options)
266
+ def delete_entry(key, **options)
169
267
  rescue_error_with(false) { @data.with { |c| c.delete(key) } }
170
268
  end
171
269
 
270
+ def serialize_entry(entry, raw: false, **options)
271
+ if raw
272
+ entry.value.to_s
273
+ else
274
+ super(entry, raw: raw, **options)
275
+ end
276
+ end
277
+
172
278
  # Memcache keys are binaries. So we need to force their encoding to binary
173
279
  # before applying the regular expression to ensure we are escaping all
174
280
  # characters properly.
175
281
  def normalize_key(key, options)
176
- key = super.dup
177
- key = key.force_encoding(Encoding::ASCII_8BIT)
178
- key = key.gsub(ESCAPE_KEY_CHARS) { |match| "%#{match.getbyte(0).to_s(16).upcase}" }
179
- key = "#{key[0, 213]}:md5:#{ActiveSupport::Digest.hexdigest(key)}" if key.size > 250
282
+ key = super
283
+ if key
284
+ key = key.dup.force_encoding(Encoding::ASCII_8BIT)
285
+ key = key.gsub(ESCAPE_KEY_CHARS) { |match| "%#{match.getbyte(0).to_s(16).upcase}" }
286
+ key = "#{key[0, 212]}:hash:#{ActiveSupport::Digest.hexdigest(key)}" if key.size > 250
287
+ end
180
288
  key
181
289
  end
182
290
 
183
- def deserialize_entry(entry)
184
- if entry
185
- entry.is_a?(Entry) ? entry : Entry.new(entry)
291
+ def deserialize_entry(payload, raw: false, **)
292
+ if payload && raw
293
+ Entry.new(payload)
294
+ else
295
+ super(payload)
186
296
  end
187
297
  end
188
298
 
189
299
  def rescue_error_with(fallback)
190
300
  yield
191
- rescue Dalli::DalliError => e
192
- logger.error("DalliError (#{e}): #{e.message}") if logger
301
+ rescue Dalli::DalliError => error
302
+ ActiveSupport.error_reporter&.report(error, handled: true, severity: :warning)
303
+ logger.error("DalliError (#{error}): #{error.message}") if logger
193
304
  fallback
194
305
  end
195
306
  end