activesupport 5.2.8.1 → 6.1.6.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 (188) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +426 -424
  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 +4 -2
  7. data/lib/active_support/backtrace_cleaner.rb +29 -3
  8. data/lib/active_support/benchmarkable.rb +1 -1
  9. data/lib/active_support/cache/file_store.rb +34 -34
  10. data/lib/active_support/cache/mem_cache_store.rb +39 -24
  11. data/lib/active_support/cache/memory_store.rb +59 -33
  12. data/lib/active_support/cache/null_store.rb +8 -3
  13. data/lib/active_support/cache/redis_cache_store.rb +72 -45
  14. data/lib/active_support/cache/strategy/local_cache.rb +41 -26
  15. data/lib/active_support/cache.rb +148 -78
  16. data/lib/active_support/callbacks.rb +81 -64
  17. data/lib/active_support/concern.rb +70 -3
  18. data/lib/active_support/concurrency/share_lock.rb +0 -1
  19. data/lib/active_support/configurable.rb +10 -14
  20. data/lib/active_support/configuration_file.rb +51 -0
  21. data/lib/active_support/core_ext/array/access.rb +18 -6
  22. data/lib/active_support/core_ext/array/conversions.rb +5 -5
  23. data/lib/active_support/core_ext/array/extract.rb +21 -0
  24. data/lib/active_support/core_ext/array.rb +1 -1
  25. data/lib/active_support/core_ext/benchmark.rb +2 -2
  26. data/lib/active_support/core_ext/class/attribute.rb +32 -47
  27. data/lib/active_support/core_ext/class/subclasses.rb +17 -38
  28. data/lib/active_support/core_ext/date/calculations.rb +6 -5
  29. data/lib/active_support/core_ext/date/conversions.rb +2 -1
  30. data/lib/active_support/core_ext/date_and_time/calculations.rb +37 -47
  31. data/lib/active_support/core_ext/date_and_time/compatibility.rb +15 -0
  32. data/lib/active_support/core_ext/date_and_time/zones.rb +0 -1
  33. data/lib/active_support/core_ext/date_time/calculations.rb +1 -1
  34. data/lib/active_support/core_ext/date_time/conversions.rb +0 -1
  35. data/lib/active_support/core_ext/digest/uuid.rb +1 -0
  36. data/lib/active_support/core_ext/enumerable.rb +171 -75
  37. data/lib/active_support/core_ext/hash/conversions.rb +3 -3
  38. data/lib/active_support/core_ext/hash/deep_transform_values.rb +46 -0
  39. data/lib/active_support/core_ext/hash/except.rb +2 -2
  40. data/lib/active_support/core_ext/hash/keys.rb +1 -30
  41. data/lib/active_support/core_ext/hash/slice.rb +6 -27
  42. data/lib/active_support/core_ext/hash.rb +1 -2
  43. data/lib/active_support/core_ext/integer/multiple.rb +1 -1
  44. data/lib/active_support/core_ext/kernel.rb +0 -1
  45. data/lib/active_support/core_ext/load_error.rb +1 -1
  46. data/lib/active_support/core_ext/marshal.rb +2 -0
  47. data/lib/active_support/core_ext/module/attr_internal.rb +2 -2
  48. data/lib/active_support/core_ext/module/attribute_accessors.rb +30 -39
  49. data/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb +17 -19
  50. data/lib/active_support/core_ext/module/concerning.rb +8 -2
  51. data/lib/active_support/core_ext/module/delegation.rb +76 -33
  52. data/lib/active_support/core_ext/module/introspection.rb +16 -15
  53. data/lib/active_support/core_ext/module/redefine_method.rb +8 -17
  54. data/lib/active_support/core_ext/module.rb +0 -1
  55. data/lib/active_support/core_ext/name_error.rb +29 -2
  56. data/lib/active_support/core_ext/numeric/conversions.rb +129 -129
  57. data/lib/active_support/core_ext/numeric.rb +0 -1
  58. data/lib/active_support/core_ext/object/blank.rb +1 -2
  59. data/lib/active_support/core_ext/object/deep_dup.rb +1 -1
  60. data/lib/active_support/core_ext/object/duplicable.rb +7 -114
  61. data/lib/active_support/core_ext/object/json.rb +14 -2
  62. data/lib/active_support/core_ext/object/try.rb +17 -7
  63. data/lib/active_support/core_ext/object/with_options.rb +1 -1
  64. data/lib/active_support/core_ext/range/compare_range.rb +34 -13
  65. data/lib/active_support/core_ext/range/conversions.rb +31 -29
  66. data/lib/active_support/core_ext/range/each.rb +0 -1
  67. data/lib/active_support/core_ext/range/include_time_with_zone.rb +8 -3
  68. data/lib/active_support/core_ext/regexp.rb +8 -5
  69. data/lib/active_support/core_ext/securerandom.rb +23 -3
  70. data/lib/active_support/core_ext/string/access.rb +5 -16
  71. data/lib/active_support/core_ext/string/conversions.rb +1 -0
  72. data/lib/active_support/core_ext/string/filters.rb +42 -1
  73. data/lib/active_support/core_ext/string/inflections.rb +45 -6
  74. data/lib/active_support/core_ext/string/inquiry.rb +1 -0
  75. data/lib/active_support/core_ext/string/multibyte.rb +6 -5
  76. data/lib/active_support/core_ext/string/output_safety.rb +70 -13
  77. data/lib/active_support/core_ext/string/starts_ends_with.rb +2 -2
  78. data/lib/active_support/core_ext/string/strip.rb +3 -1
  79. data/lib/active_support/core_ext/symbol/starts_ends_with.rb +14 -0
  80. data/lib/active_support/core_ext/symbol.rb +3 -0
  81. data/lib/active_support/core_ext/time/calculations.rb +53 -3
  82. data/lib/active_support/core_ext/time/conversions.rb +2 -0
  83. data/lib/active_support/core_ext/uri.rb +6 -1
  84. data/lib/active_support/core_ext.rb +1 -1
  85. data/lib/active_support/current_attributes/test_helper.rb +13 -0
  86. data/lib/active_support/current_attributes.rb +16 -2
  87. data/lib/active_support/dependencies/zeitwerk_integration.rb +120 -0
  88. data/lib/active_support/dependencies.rb +109 -34
  89. data/lib/active_support/deprecation/behaviors.rb +16 -3
  90. data/lib/active_support/deprecation/disallowed.rb +56 -0
  91. data/lib/active_support/deprecation/instance_delegator.rb +0 -1
  92. data/lib/active_support/deprecation/method_wrappers.rb +18 -23
  93. data/lib/active_support/deprecation/proxy_wrappers.rb +29 -6
  94. data/lib/active_support/deprecation/reporting.rb +50 -7
  95. data/lib/active_support/deprecation.rb +6 -1
  96. data/lib/active_support/descendants_tracker.rb +59 -9
  97. data/lib/active_support/digest.rb +2 -0
  98. data/lib/active_support/duration/iso8601_parser.rb +2 -4
  99. data/lib/active_support/duration/iso8601_serializer.rb +18 -14
  100. data/lib/active_support/duration.rb +82 -33
  101. data/lib/active_support/encrypted_configuration.rb +0 -4
  102. data/lib/active_support/encrypted_file.rb +22 -4
  103. data/lib/active_support/environment_inquirer.rb +20 -0
  104. data/lib/active_support/evented_file_update_checker.rb +82 -117
  105. data/lib/active_support/execution_wrapper.rb +2 -1
  106. data/lib/active_support/file_update_checker.rb +0 -1
  107. data/lib/active_support/fork_tracker.rb +64 -0
  108. data/lib/active_support/gem_version.rb +3 -3
  109. data/lib/active_support/hash_with_indifferent_access.rb +70 -42
  110. data/lib/active_support/i18n.rb +1 -0
  111. data/lib/active_support/i18n_railtie.rb +15 -8
  112. data/lib/active_support/inflector/inflections.rb +2 -7
  113. data/lib/active_support/inflector/methods.rb +49 -58
  114. data/lib/active_support/inflector/transliterate.rb +47 -18
  115. data/lib/active_support/json/decoding.rb +25 -26
  116. data/lib/active_support/json/encoding.rb +11 -3
  117. data/lib/active_support/key_generator.rb +1 -33
  118. data/lib/active_support/lazy_load_hooks.rb +5 -2
  119. data/lib/active_support/locale/en.rb +33 -0
  120. data/lib/active_support/locale/en.yml +7 -3
  121. data/lib/active_support/log_subscriber.rb +39 -9
  122. data/lib/active_support/logger.rb +2 -17
  123. data/lib/active_support/logger_silence.rb +11 -19
  124. data/lib/active_support/logger_thread_safe_level.rb +50 -6
  125. data/lib/active_support/message_encryptor.rb +8 -13
  126. data/lib/active_support/message_verifier.rb +10 -10
  127. data/lib/active_support/messages/metadata.rb +11 -2
  128. data/lib/active_support/messages/rotation_configuration.rb +2 -1
  129. data/lib/active_support/messages/rotator.rb +10 -9
  130. data/lib/active_support/multibyte/chars.rb +10 -68
  131. data/lib/active_support/multibyte/unicode.rb +15 -327
  132. data/lib/active_support/notifications/fanout.rb +116 -16
  133. data/lib/active_support/notifications/instrumenter.rb +71 -9
  134. data/lib/active_support/notifications.rb +72 -8
  135. data/lib/active_support/number_helper/number_converter.rb +5 -6
  136. data/lib/active_support/number_helper/number_to_currency_converter.rb +4 -9
  137. data/lib/active_support/number_helper/number_to_delimited_converter.rb +3 -2
  138. data/lib/active_support/number_helper/number_to_human_converter.rb +4 -3
  139. data/lib/active_support/number_helper/number_to_human_size_converter.rb +4 -3
  140. data/lib/active_support/number_helper/number_to_percentage_converter.rb +3 -1
  141. data/lib/active_support/number_helper/number_to_phone_converter.rb +2 -1
  142. data/lib/active_support/number_helper/number_to_rounded_converter.rb +12 -7
  143. data/lib/active_support/number_helper/rounding_helper.rb +12 -28
  144. data/lib/active_support/number_helper.rb +38 -12
  145. data/lib/active_support/option_merger.rb +22 -3
  146. data/lib/active_support/ordered_hash.rb +1 -1
  147. data/lib/active_support/ordered_options.rb +13 -3
  148. data/lib/active_support/parameter_filter.rb +133 -0
  149. data/lib/active_support/per_thread_registry.rb +2 -1
  150. data/lib/active_support/rails.rb +1 -10
  151. data/lib/active_support/railtie.rb +23 -1
  152. data/lib/active_support/reloader.rb +4 -5
  153. data/lib/active_support/rescuable.rb +4 -4
  154. data/lib/active_support/secure_compare_rotator.rb +51 -0
  155. data/lib/active_support/security_utils.rb +19 -12
  156. data/lib/active_support/string_inquirer.rb +4 -3
  157. data/lib/active_support/subscriber.rb +72 -28
  158. data/lib/active_support/tagged_logging.rb +42 -8
  159. data/lib/active_support/test_case.rb +91 -0
  160. data/lib/active_support/testing/assertions.rb +30 -9
  161. data/lib/active_support/testing/deprecation.rb +0 -1
  162. data/lib/active_support/testing/file_fixtures.rb +2 -0
  163. data/lib/active_support/testing/isolation.rb +2 -2
  164. data/lib/active_support/testing/method_call_assertions.rb +28 -1
  165. data/lib/active_support/testing/parallelization/server.rb +78 -0
  166. data/lib/active_support/testing/parallelization/worker.rb +100 -0
  167. data/lib/active_support/testing/parallelization.rb +51 -0
  168. data/lib/active_support/testing/stream.rb +1 -2
  169. data/lib/active_support/testing/time_helpers.rb +47 -12
  170. data/lib/active_support/time_with_zone.rb +81 -47
  171. data/lib/active_support/values/time_zone.rb +34 -17
  172. data/lib/active_support/xml_mini/jdom.rb +2 -3
  173. data/lib/active_support/xml_mini/libxml.rb +2 -2
  174. data/lib/active_support/xml_mini/libxmlsax.rb +4 -4
  175. data/lib/active_support/xml_mini/nokogiri.rb +2 -2
  176. data/lib/active_support/xml_mini/nokogirisax.rb +3 -3
  177. data/lib/active_support/xml_mini/rexml.rb +10 -3
  178. data/lib/active_support/xml_mini.rb +2 -10
  179. data/lib/active_support.rb +14 -1
  180. metadata +54 -27
  181. data/lib/active_support/core_ext/array/prepend_and_append.rb +0 -9
  182. data/lib/active_support/core_ext/hash/compact.rb +0 -29
  183. data/lib/active_support/core_ext/hash/transform_values.rb +0 -32
  184. data/lib/active_support/core_ext/kernel/agnostics.rb +0 -13
  185. data/lib/active_support/core_ext/module/reachable.rb +0 -11
  186. data/lib/active_support/core_ext/numeric/inquiry.rb +0 -28
  187. data/lib/active_support/core_ext/range/include_range.rb +0 -3
  188. 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 let's 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
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "active_support/core_ext/symbol/starts_ends_with"
4
+
3
5
  module ActiveSupport
4
6
  # Wrapping an array in an +ArrayInquirer+ gives a friendlier way to check
5
7
  # its string-like contents:
@@ -34,11 +36,11 @@ module ActiveSupport
34
36
 
35
37
  private
36
38
  def respond_to_missing?(name, include_private = false)
37
- (name[-1] == "?") || super
39
+ name.end_with?("?") || super
38
40
  end
39
41
 
40
42
  def method_missing(name, *args)
41
- if name[-1] == "?"
43
+ if name.end_with?("?")
42
44
  any?(name[0..-2])
43
45
  else
44
46
  super
@@ -16,7 +16,7 @@ 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)
@@ -31,6 +31,9 @@ module ActiveSupport
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
@@ -41,7 +41,7 @@ module ActiveSupport
41
41
 
42
42
  result = nil
43
43
  ms = Benchmark.ms { result = options[:silence] ? logger.silence { yield } : yield }
44
- logger.send(options[:level], "%s (%.1fms)" % [ message, ms ])
44
+ logger.public_send(options[:level], "%s (%.1fms)" % [ message, ms ])
45
45
  result
46
46
  else
47
47
  yield
@@ -16,31 +16,35 @@ module ActiveSupport
16
16
  attr_reader :cache_path
17
17
 
18
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)
19
+ 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
20
  FILEPATH_MAX_SIZE = 900 # max is 1024, plus some room
21
- EXCLUDED_DIRS = [".", ".."].freeze
22
21
  GITKEEP_FILES = [".gitkeep", ".keep"].freeze
23
22
 
24
- def initialize(cache_path, options = nil)
23
+ def initialize(cache_path, **options)
25
24
  super(options)
26
25
  @cache_path = cache_path.to_s
27
26
  end
28
27
 
28
+ # Advertise cache versioning support.
29
+ def self.supports_cache_versioning?
30
+ true
31
+ end
32
+
29
33
  # Deletes all items from the cache. In this case it deletes all the entries in the specified
30
34
  # file store directory except for .keep or .gitkeep. Be careful which directory is specified in your
31
35
  # config file when using +FileStore+ because everything in that directory will be deleted.
32
36
  def clear(options = nil)
33
- root_dirs = exclude_from(cache_path, EXCLUDED_DIRS + GITKEEP_FILES)
37
+ root_dirs = (Dir.children(cache_path) - GITKEEP_FILES)
34
38
  FileUtils.rm_r(root_dirs.collect { |f| File.join(cache_path, f) })
35
- rescue Errno::ENOENT
39
+ rescue Errno::ENOENT, Errno::ENOTEMPTY
36
40
  end
37
41
 
38
42
  # Preemptively iterates through all stored keys and removes the ones which have expired.
39
43
  def cleanup(options = nil)
40
44
  options = merged_options(options)
41
45
  search_dir(cache_path) do |fname|
42
- entry = read_entry(fname, options)
43
- delete_entry(fname, options) if entry && entry.expired?
46
+ entry = read_entry(fname, **options)
47
+ delete_entry(fname, **options) if entry && entry.expired?
44
48
  end
45
49
  end
46
50
 
@@ -62,30 +66,30 @@ module ActiveSupport
62
66
  matcher = key_matcher(matcher, options)
63
67
  search_dir(cache_path) do |path|
64
68
  key = file_path_key(path)
65
- delete_entry(path, options) if key.match(matcher)
69
+ delete_entry(path, **options) if key.match(matcher)
66
70
  end
67
71
  end
68
72
  end
69
73
 
70
74
  private
71
-
72
- def read_entry(key, options)
75
+ def read_entry(key, **options)
73
76
  if File.exist?(key)
74
- File.open(key) { |f| Marshal.load(f) }
77
+ entry = File.open(key) { |f| deserialize_entry(f.read) }
78
+ entry if entry.is_a?(Cache::Entry)
75
79
  end
76
80
  rescue => e
77
81
  logger.error("FileStoreError (#{e}): #{e.message}") if logger
78
82
  nil
79
83
  end
80
84
 
81
- def write_entry(key, entry, options)
85
+ def write_entry(key, entry, **options)
82
86
  return false if options[:unless_exist] && File.exist?(key)
83
87
  ensure_cache_path(File.dirname(key))
84
- File.atomic_write(key, cache_path) { |f| Marshal.dump(entry, f) }
88
+ File.atomic_write(key, cache_path) { |f| f.write(serialize_entry(entry)) }
85
89
  true
86
90
  end
87
91
 
88
- def delete_entry(key, options)
92
+ def delete_entry(key, **options)
89
93
  if File.exist?(key)
90
94
  begin
91
95
  File.delete(key)
@@ -103,12 +107,10 @@ module ActiveSupport
103
107
  def lock_file(file_name, &block)
104
108
  if File.exist?(file_name)
105
109
  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
110
+ f.flock File::LOCK_EX
111
+ yield
112
+ ensure
113
+ f.flock File::LOCK_UN
112
114
  end
113
115
  else
114
116
  yield
@@ -127,15 +129,19 @@ module ActiveSupport
127
129
  hash = Zlib.adler32(fname)
128
130
  hash, dir_1 = hash.divmod(0x1000)
129
131
  dir_2 = hash.modulo(0x1000)
130
- fname_paths = []
131
132
 
132
133
  # 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?
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
137
143
 
138
- File.join(cache_path, DIR_FORMATTER % dir_1, DIR_FORMATTER % dir_2, *fname_paths)
144
+ File.join(cache_path, DIR_FORMATTER % dir_1, DIR_FORMATTER % dir_2, fname_paths)
139
145
  end
140
146
 
141
147
  # Translate a file path into a key.
@@ -147,7 +153,7 @@ module ActiveSupport
147
153
  # Delete empty directories in the cache.
148
154
  def delete_empty_directories(dir)
149
155
  return if File.realpath(dir) == File.realpath(cache_path)
150
- if exclude_from(dir, EXCLUDED_DIRS).empty?
156
+ if Dir.children(dir).empty?
151
157
  Dir.delete(dir) rescue nil
152
158
  delete_empty_directories(File.dirname(dir))
153
159
  end
@@ -160,8 +166,7 @@ module ActiveSupport
160
166
 
161
167
  def search_dir(dir, &callback)
162
168
  return if !File.exist?(dir)
163
- Dir.foreach(dir) do |d|
164
- next if EXCLUDED_DIRS.include?(d)
169
+ Dir.each_child(dir) do |d|
165
170
  name = File.join(dir, d)
166
171
  if File.directory?(name)
167
172
  search_dir(name, &callback)
@@ -186,11 +191,6 @@ module ActiveSupport
186
191
  end
187
192
  end
188
193
  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
194
  end
195
195
  end
196
196
  end
@@ -7,6 +7,7 @@ rescue LoadError => e
7
7
  raise e
8
8
  end
9
9
 
10
+ require "active_support/core_ext/enumerable"
10
11
  require "active_support/core_ext/marshal"
11
12
  require "active_support/core_ext/array/extract_options"
12
13
 
@@ -25,36 +26,45 @@ module ActiveSupport
25
26
  # MemCacheStore implements the Strategy::LocalCache strategy which implements
26
27
  # an in-memory cache inside of a block.
27
28
  class MemCacheStore < Store
29
+ DEFAULT_CODER = NullCoder # Dalli automatically Marshal values
30
+
28
31
  # Provide support for raw values in the local cache strategy.
29
32
  module LocalCacheWithRaw # :nodoc:
30
33
  private
31
- def write_entry(key, entry, options)
34
+ def write_entry(key, entry, **options)
32
35
  if options[:raw] && local_cache
33
36
  raw_entry = Entry.new(entry.value.to_s)
34
37
  raw_entry.expires_at = entry.expires_at
35
- super(key, raw_entry, options)
38
+ super(key, raw_entry, **options)
36
39
  else
37
40
  super
38
41
  end
39
42
  end
40
43
  end
41
44
 
45
+ # Advertise cache versioning support.
46
+ def self.supports_cache_versioning?
47
+ true
48
+ end
49
+
42
50
  prepend Strategy::LocalCache
43
51
  prepend LocalCacheWithRaw
44
52
 
45
53
  ESCAPE_KEY_CHARS = /[\x00-\x20%\x7F-\xFF]/n
46
54
 
47
55
  # Creates a new Dalli::Client instance with specified addresses and options.
48
- # By default address is equal localhost:11211.
56
+ # If no addresses are provided, we give nil to Dalli::Client, so it uses its fallbacks:
57
+ # - ENV["MEMCACHE_SERVERS"] (if defined)
58
+ # - "127.0.0.1:11211" (otherwise)
49
59
  #
50
60
  # ActiveSupport::Cache::MemCacheStore.build_mem_cache
51
- # # => #<Dalli::Client:0x007f98a47d2028 @servers=["localhost:11211"], @options={}, @ring=nil>
61
+ # # => #<Dalli::Client:0x007f98a47d2028 @servers=["127.0.0.1:11211"], @options={}, @ring=nil>
52
62
  # ActiveSupport::Cache::MemCacheStore.build_mem_cache('localhost:10290')
53
63
  # # => #<Dalli::Client:0x007f98a47b3a60 @servers=["localhost:10290"], @options={}, @ring=nil>
54
64
  def self.build_mem_cache(*addresses) # :nodoc:
55
65
  addresses = addresses.flatten
56
66
  options = addresses.extract_options!
57
- addresses = ["localhost:11211"] if addresses.empty?
67
+ addresses = nil if addresses.compact.empty?
58
68
  pool_options = retrieve_pool_options(options)
59
69
 
60
70
  if pool_options.empty?
@@ -71,8 +81,8 @@ module ActiveSupport
71
81
  #
72
82
  # ActiveSupport::Cache::MemCacheStore.new("localhost", "server-downstairs.localnetwork:8229")
73
83
  #
74
- # If no addresses are specified, then MemCacheStore will connect to
75
- # localhost port 11211 (the default memcached port).
84
+ # If no addresses are provided, but ENV['MEMCACHE_SERVERS'] is defined, it will be used instead. Otherwise,
85
+ # MemCacheStore will connect to localhost:11211 (the default memcached port).
76
86
  def initialize(*addresses)
77
87
  addresses = addresses.flatten
78
88
  options = addresses.extract_options!
@@ -129,27 +139,28 @@ module ActiveSupport
129
139
 
130
140
  private
131
141
  # Read an entry from the cache.
132
- def read_entry(key, options)
142
+ def read_entry(key, **options)
133
143
  rescue_error_with(nil) { deserialize_entry(@data.with { |c| c.get(key, options) }) }
134
144
  end
135
145
 
136
146
  # Write an entry to the cache.
137
- def write_entry(key, entry, options)
138
- method = options && options[:unless_exist] ? :add : :set
139
- value = options[:raw] ? entry.value.to_s : entry
147
+ def write_entry(key, entry, **options)
148
+ method = options[:unless_exist] ? :add : :set
149
+ value = options[:raw] ? entry.value.to_s : serialize_entry(entry)
140
150
  expires_in = options[:expires_in].to_i
141
- if expires_in > 0 && !options[:raw]
151
+ if options[:race_condition_ttl] && expires_in > 0 && !options[:raw]
142
152
  # Set the memcache expire a few minutes in the future to support race condition ttls on read
143
153
  expires_in += 5.minutes
144
154
  end
145
155
  rescue_error_with false do
146
- @data.with { |c| c.send(method, key, value, expires_in, options) }
156
+ # The value "compress: false" prevents duplicate compression within Dalli.
157
+ @data.with { |c| c.send(method, key, value, expires_in, **options, compress: false) }
147
158
  end
148
159
  end
149
160
 
150
161
  # Reads multiple entries from the cache implementation.
151
- def read_multi_entries(names, options)
152
- keys_to_names = Hash[names.map { |name| [normalize_key(name, options), name] }]
162
+ def read_multi_entries(names, **options)
163
+ keys_to_names = names.index_by { |name| normalize_key(name, options) }
153
164
 
154
165
  raw_values = @data.with { |c| c.get_multi(keys_to_names.keys) }
155
166
  values = {}
@@ -166,7 +177,7 @@ module ActiveSupport
166
177
  end
167
178
 
168
179
  # Delete an entry from the cache.
169
- def delete_entry(key, options)
180
+ def delete_entry(key, **options)
170
181
  rescue_error_with(false) { @data.with { |c| c.delete(key) } }
171
182
  end
172
183
 
@@ -174,17 +185,21 @@ module ActiveSupport
174
185
  # before applying the regular expression to ensure we are escaping all
175
186
  # characters properly.
176
187
  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:#{ActiveSupport::Digest.hexdigest(key)}" if key.size > 250
188
+ key = super
189
+
190
+ if key
191
+ key = key.dup.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
+ end
195
+
181
196
  key
182
197
  end
183
198
 
184
- def deserialize_entry(entry)
185
- if entry
186
- entry.is_a?(Entry) ? entry : Entry.new(entry)
187
- end
199
+ def deserialize_entry(payload)
200
+ entry = super
201
+ entry = Entry.new(entry, compress: false) unless entry.nil? || entry.is_a?(Entry)
202
+ entry
188
203
  end
189
204
 
190
205
  def rescue_error_with(fallback)
@@ -16,13 +16,37 @@ module ActiveSupport
16
16
  # a cleanup will occur which tries to prune the cache down to three quarters
17
17
  # of the maximum size by removing the least recently used entries.
18
18
  #
19
+ # Unlike other Cache store implementations, MemoryStore does not compress
20
+ # values by default. MemoryStore does not benefit from compression as much
21
+ # as other Store implementations, as it does not send data over a network.
22
+ # However, when compression is enabled, it still pays the full cost of
23
+ # compression in terms of cpu use.
24
+ #
19
25
  # MemoryStore is thread-safe.
20
26
  class MemoryStore < Store
27
+ module DupCoder # :nodoc:
28
+ class << self
29
+ def load(entry)
30
+ entry = entry.dup
31
+ entry.dup_value!
32
+ entry
33
+ end
34
+
35
+ def dump(entry)
36
+ entry.dup_value!
37
+ entry
38
+ end
39
+ end
40
+ end
41
+
42
+ DEFAULT_CODER = DupCoder
43
+
21
44
  def initialize(options = nil)
22
45
  options ||= {}
46
+ # Disable compression by default.
47
+ options[:compress] ||= false
23
48
  super(options)
24
49
  @data = {}
25
- @key_access = {}
26
50
  @max_size = options[:size] || 32.megabytes
27
51
  @max_prune_time = options[:max_prune_time] || 2
28
52
  @cache_size = 0
@@ -30,11 +54,15 @@ module ActiveSupport
30
54
  @pruning = false
31
55
  end
32
56
 
57
+ # Advertise cache versioning support.
58
+ def self.supports_cache_versioning?
59
+ true
60
+ end
61
+
33
62
  # Delete all data stored in a given cache store.
34
63
  def clear(options = nil)
35
64
  synchronize do
36
65
  @data.clear
37
- @key_access.clear
38
66
  @cache_size = 0
39
67
  end
40
68
  end
@@ -46,7 +74,7 @@ module ActiveSupport
46
74
  keys = synchronize { @data.keys }
47
75
  keys.each do |key|
48
76
  entry = @data[key]
49
- delete_entry(key, options) if entry && entry.expired?
77
+ delete_entry(key, **options) if entry && entry.expired?
50
78
  end
51
79
  end
52
80
  end
@@ -57,13 +85,13 @@ module ActiveSupport
57
85
  return if pruning?
58
86
  @pruning = true
59
87
  begin
60
- start_time = Time.now
88
+ start_time = Concurrent.monotonic_time
61
89
  cleanup
62
90
  instrument(:prune, target_size, from: @cache_size) do
63
- keys = synchronize { @key_access.keys.sort { |a, b| @key_access[a].to_f <=> @key_access[b].to_f } }
91
+ keys = synchronize { @data.keys }
64
92
  keys.each do |key|
65
- delete_entry(key, options)
66
- return if @cache_size <= target_size || (max_time && Time.now - start_time > max_time)
93
+ delete_entry(key, **options)
94
+ return if @cache_size <= target_size || (max_time && Concurrent.monotonic_time - start_time > max_time)
67
95
  end
68
96
  end
69
97
  ensure
@@ -93,13 +121,13 @@ module ActiveSupport
93
121
  matcher = key_matcher(matcher, options)
94
122
  keys = synchronize { @data.keys }
95
123
  keys.each do |key|
96
- delete_entry(key, options) if key.match(matcher)
124
+ delete_entry(key, **options) if key.match(matcher)
97
125
  end
98
126
  end
99
127
  end
100
128
 
101
129
  def inspect # :nodoc:
102
- "<##{self.class.name} entries=#{@data.size}, size=#{@cache_size}, options=#{@options.inspect}>"
130
+ "#<#{self.class.name} entries=#{@data.size}, size=#{@cache_size}, options=#{@options.inspect}>"
103
131
  end
104
132
 
105
133
  # Synchronize calls to the cache. This should be called wherever the underlying cache implementation
@@ -109,54 +137,52 @@ module ActiveSupport
109
137
  end
110
138
 
111
139
  private
112
-
113
140
  PER_ENTRY_OVERHEAD = 240
114
141
 
115
- def cached_size(key, entry)
116
- key.to_s.bytesize + entry.size + PER_ENTRY_OVERHEAD
142
+ def cached_size(key, payload)
143
+ key.to_s.bytesize + payload.bytesize + PER_ENTRY_OVERHEAD
117
144
  end
118
145
 
119
- def read_entry(key, options)
120
- entry = @data[key]
146
+ def read_entry(key, **options)
147
+ entry = nil
121
148
  synchronize do
122
- if entry
123
- @key_access[key] = Time.now.to_f
124
- else
125
- @key_access.delete(key)
149
+ payload = @data.delete(key)
150
+ if payload
151
+ @data[key] = payload
152
+ entry = deserialize_entry(payload)
126
153
  end
127
154
  end
128
155
  entry
129
156
  end
130
157
 
131
- def write_entry(key, entry, options)
132
- entry.dup_value!
158
+ def write_entry(key, entry, **options)
159
+ payload = serialize_entry(entry)
133
160
  synchronize do
134
- old_entry = @data[key]
135
- return false if @data.key?(key) && options[:unless_exist]
136
- if old_entry
137
- @cache_size -= (old_entry.size - entry.size)
161
+ return false if options[:unless_exist] && @data.key?(key)
162
+
163
+ old_payload = @data[key]
164
+ if old_payload
165
+ @cache_size -= (old_payload.bytesize - payload.bytesize)
138
166
  else
139
- @cache_size += cached_size(key, entry)
167
+ @cache_size += cached_size(key, payload)
140
168
  end
141
- @key_access[key] = Time.now.to_f
142
- @data[key] = entry
169
+ @data[key] = payload
143
170
  prune(@max_size * 0.75, @max_prune_time) if @cache_size > @max_size
144
171
  true
145
172
  end
146
173
  end
147
174
 
148
- def delete_entry(key, options)
175
+ def delete_entry(key, **options)
149
176
  synchronize do
150
- @key_access.delete(key)
151
- entry = @data.delete(key)
152
- @cache_size -= cached_size(key, entry) if entry
153
- !!entry
177
+ payload = @data.delete(key)
178
+ @cache_size -= cached_size(key, payload) if payload
179
+ !!payload
154
180
  end
155
181
  end
156
182
 
157
183
  def modify_value(name, amount, options)
184
+ options = merged_options(options)
158
185
  synchronize do
159
- options = merged_options(options)
160
186
  if num = read(name, options)
161
187
  num = num.to_i + amount
162
188
  write(name, num, options)