activesupport 5.2.0 → 6.1.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 (190) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +362 -333
  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 +33 -33
  10. data/lib/active_support/cache/mem_cache_store.rb +31 -29
  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 +84 -45
  14. data/lib/active_support/cache/strategy/local_cache.rb +41 -26
  15. data/lib/active_support/cache.rb +174 -113
  16. data/lib/active_support/callbacks.rb +81 -64
  17. data/lib/active_support/concern.rb +76 -5
  18. data/lib/active_support/concurrency/load_interlock_aware_monitor.rb +18 -0
  19. data/lib/active_support/concurrency/share_lock.rb +0 -1
  20. data/lib/active_support/configurable.rb +10 -14
  21. data/lib/active_support/configuration_file.rb +46 -0
  22. data/lib/active_support/core_ext/array/access.rb +18 -6
  23. data/lib/active_support/core_ext/array/conversions.rb +5 -5
  24. data/lib/active_support/core_ext/array/extract.rb +21 -0
  25. data/lib/active_support/core_ext/array.rb +1 -1
  26. data/lib/active_support/core_ext/benchmark.rb +2 -2
  27. data/lib/active_support/core_ext/class/attribute.rb +32 -47
  28. data/lib/active_support/core_ext/class/subclasses.rb +17 -38
  29. data/lib/active_support/core_ext/date/calculations.rb +6 -5
  30. data/lib/active_support/core_ext/date/conversions.rb +2 -1
  31. data/lib/active_support/core_ext/date_and_time/calculations.rb +37 -47
  32. data/lib/active_support/core_ext/date_and_time/compatibility.rb +15 -0
  33. data/lib/active_support/core_ext/date_and_time/zones.rb +0 -1
  34. data/lib/active_support/core_ext/date_time/calculations.rb +1 -1
  35. data/lib/active_support/core_ext/date_time/conversions.rb +0 -1
  36. data/lib/active_support/core_ext/digest.rb +3 -0
  37. data/lib/active_support/core_ext/enumerable.rb +171 -70
  38. data/lib/active_support/core_ext/file/atomic.rb +1 -1
  39. data/lib/active_support/core_ext/hash/conversions.rb +3 -3
  40. data/lib/active_support/core_ext/hash/deep_transform_values.rb +46 -0
  41. data/lib/active_support/core_ext/hash/except.rb +2 -2
  42. data/lib/active_support/core_ext/hash/keys.rb +1 -30
  43. data/lib/active_support/core_ext/hash/slice.rb +6 -27
  44. data/lib/active_support/core_ext/hash.rb +1 -2
  45. data/lib/active_support/core_ext/integer/multiple.rb +1 -1
  46. data/lib/active_support/core_ext/kernel.rb +0 -1
  47. data/lib/active_support/core_ext/load_error.rb +1 -1
  48. data/lib/active_support/core_ext/marshal.rb +2 -0
  49. data/lib/active_support/core_ext/module/attr_internal.rb +2 -2
  50. data/lib/active_support/core_ext/module/attribute_accessors.rb +30 -39
  51. data/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb +17 -19
  52. data/lib/active_support/core_ext/module/concerning.rb +8 -2
  53. data/lib/active_support/core_ext/module/delegation.rb +76 -33
  54. data/lib/active_support/core_ext/module/introspection.rb +16 -15
  55. data/lib/active_support/core_ext/module/redefine_method.rb +8 -17
  56. data/lib/active_support/core_ext/module.rb +0 -1
  57. data/lib/active_support/core_ext/name_error.rb +29 -2
  58. data/lib/active_support/core_ext/numeric/conversions.rb +129 -129
  59. data/lib/active_support/core_ext/numeric.rb +0 -1
  60. data/lib/active_support/core_ext/object/blank.rb +1 -2
  61. data/lib/active_support/core_ext/object/deep_dup.rb +1 -1
  62. data/lib/active_support/core_ext/object/duplicable.rb +7 -114
  63. data/lib/active_support/core_ext/object/json.rb +7 -2
  64. data/lib/active_support/core_ext/object/to_query.rb +5 -2
  65. data/lib/active_support/core_ext/object/try.rb +17 -7
  66. data/lib/active_support/core_ext/object/with_options.rb +1 -1
  67. data/lib/active_support/core_ext/range/compare_range.rb +82 -0
  68. data/lib/active_support/core_ext/range/conversions.rb +31 -29
  69. data/lib/active_support/core_ext/range/each.rb +0 -1
  70. data/lib/active_support/core_ext/range/include_time_with_zone.rb +8 -3
  71. data/lib/active_support/core_ext/range.rb +1 -1
  72. data/lib/active_support/core_ext/regexp.rb +8 -5
  73. data/lib/active_support/core_ext/securerandom.rb +23 -3
  74. data/lib/active_support/core_ext/string/access.rb +5 -16
  75. data/lib/active_support/core_ext/string/conversions.rb +1 -0
  76. data/lib/active_support/core_ext/string/filters.rb +42 -1
  77. data/lib/active_support/core_ext/string/inflections.rb +45 -6
  78. data/lib/active_support/core_ext/string/inquiry.rb +1 -0
  79. data/lib/active_support/core_ext/string/multibyte.rb +6 -5
  80. data/lib/active_support/core_ext/string/output_safety.rb +69 -12
  81. data/lib/active_support/core_ext/string/starts_ends_with.rb +2 -2
  82. data/lib/active_support/core_ext/string/strip.rb +3 -1
  83. data/lib/active_support/core_ext/symbol/starts_ends_with.rb +14 -0
  84. data/lib/active_support/core_ext/symbol.rb +3 -0
  85. data/lib/active_support/core_ext/time/calculations.rb +50 -3
  86. data/lib/active_support/core_ext/time/conversions.rb +1 -0
  87. data/lib/active_support/core_ext/uri.rb +7 -5
  88. data/lib/active_support/current_attributes/test_helper.rb +13 -0
  89. data/lib/active_support/current_attributes.rb +15 -2
  90. data/lib/active_support/dependencies/zeitwerk_integration.rb +117 -0
  91. data/lib/active_support/dependencies.rb +118 -35
  92. data/lib/active_support/deprecation/behaviors.rb +20 -3
  93. data/lib/active_support/deprecation/disallowed.rb +56 -0
  94. data/lib/active_support/deprecation/instance_delegator.rb +0 -1
  95. data/lib/active_support/deprecation/method_wrappers.rb +21 -13
  96. data/lib/active_support/deprecation/proxy_wrappers.rb +29 -6
  97. data/lib/active_support/deprecation/reporting.rb +51 -8
  98. data/lib/active_support/deprecation.rb +6 -1
  99. data/lib/active_support/descendants_tracker.rb +59 -9
  100. data/lib/active_support/duration/iso8601_parser.rb +2 -4
  101. data/lib/active_support/duration/iso8601_serializer.rb +18 -14
  102. data/lib/active_support/duration.rb +90 -38
  103. data/lib/active_support/encrypted_configuration.rb +1 -5
  104. data/lib/active_support/encrypted_file.rb +23 -5
  105. data/lib/active_support/environment_inquirer.rb +20 -0
  106. data/lib/active_support/evented_file_update_checker.rb +82 -117
  107. data/lib/active_support/execution_wrapper.rb +1 -0
  108. data/lib/active_support/file_update_checker.rb +0 -1
  109. data/lib/active_support/fork_tracker.rb +62 -0
  110. data/lib/active_support/gem_version.rb +2 -2
  111. data/lib/active_support/hash_with_indifferent_access.rb +78 -41
  112. data/lib/active_support/i18n.rb +1 -0
  113. data/lib/active_support/i18n_railtie.rb +16 -5
  114. data/lib/active_support/inflector/inflections.rb +2 -7
  115. data/lib/active_support/inflector/methods.rb +50 -57
  116. data/lib/active_support/inflector/transliterate.rb +47 -18
  117. data/lib/active_support/json/decoding.rb +25 -26
  118. data/lib/active_support/json/encoding.rb +11 -3
  119. data/lib/active_support/key_generator.rb +1 -33
  120. data/lib/active_support/lazy_load_hooks.rb +5 -2
  121. data/lib/active_support/locale/en.rb +33 -0
  122. data/lib/active_support/locale/en.yml +7 -3
  123. data/lib/active_support/log_subscriber.rb +39 -9
  124. data/lib/active_support/logger.rb +2 -17
  125. data/lib/active_support/logger_silence.rb +11 -19
  126. data/lib/active_support/logger_thread_safe_level.rb +52 -7
  127. data/lib/active_support/message_encryptor.rb +8 -13
  128. data/lib/active_support/message_verifier.rb +10 -10
  129. data/lib/active_support/messages/metadata.rb +11 -2
  130. data/lib/active_support/messages/rotation_configuration.rb +2 -1
  131. data/lib/active_support/messages/rotator.rb +10 -9
  132. data/lib/active_support/multibyte/chars.rb +10 -68
  133. data/lib/active_support/multibyte/unicode.rb +15 -327
  134. data/lib/active_support/notifications/fanout.rb +116 -16
  135. data/lib/active_support/notifications/instrumenter.rb +71 -9
  136. data/lib/active_support/notifications.rb +72 -8
  137. data/lib/active_support/number_helper/number_converter.rb +5 -6
  138. data/lib/active_support/number_helper/number_to_currency_converter.rb +4 -9
  139. data/lib/active_support/number_helper/number_to_delimited_converter.rb +3 -2
  140. data/lib/active_support/number_helper/number_to_human_converter.rb +4 -3
  141. data/lib/active_support/number_helper/number_to_human_size_converter.rb +4 -3
  142. data/lib/active_support/number_helper/number_to_percentage_converter.rb +3 -1
  143. data/lib/active_support/number_helper/number_to_phone_converter.rb +2 -1
  144. data/lib/active_support/number_helper/number_to_rounded_converter.rb +8 -7
  145. data/lib/active_support/number_helper/rounding_helper.rb +12 -28
  146. data/lib/active_support/number_helper.rb +38 -12
  147. data/lib/active_support/option_merger.rb +22 -3
  148. data/lib/active_support/ordered_hash.rb +1 -1
  149. data/lib/active_support/ordered_options.rb +13 -3
  150. data/lib/active_support/parameter_filter.rb +133 -0
  151. data/lib/active_support/per_thread_registry.rb +1 -1
  152. data/lib/active_support/rails.rb +1 -10
  153. data/lib/active_support/railtie.rb +23 -1
  154. data/lib/active_support/reloader.rb +4 -5
  155. data/lib/active_support/secure_compare_rotator.rb +51 -0
  156. data/lib/active_support/security_utils.rb +19 -12
  157. data/lib/active_support/string_inquirer.rb +4 -3
  158. data/lib/active_support/subscriber.rb +72 -24
  159. data/lib/active_support/tagged_logging.rb +42 -8
  160. data/lib/active_support/test_case.rb +92 -1
  161. data/lib/active_support/testing/assertions.rb +30 -9
  162. data/lib/active_support/testing/deprecation.rb +0 -1
  163. data/lib/active_support/testing/file_fixtures.rb +2 -0
  164. data/lib/active_support/testing/isolation.rb +2 -2
  165. data/lib/active_support/testing/method_call_assertions.rb +28 -1
  166. data/lib/active_support/testing/parallelization/server.rb +78 -0
  167. data/lib/active_support/testing/parallelization/worker.rb +100 -0
  168. data/lib/active_support/testing/parallelization.rb +51 -0
  169. data/lib/active_support/testing/setup_and_teardown.rb +5 -9
  170. data/lib/active_support/testing/stream.rb +1 -2
  171. data/lib/active_support/testing/time_helpers.rb +47 -12
  172. data/lib/active_support/time_with_zone.rb +81 -47
  173. data/lib/active_support/values/time_zone.rb +34 -18
  174. data/lib/active_support/xml_mini/jdom.rb +2 -3
  175. data/lib/active_support/xml_mini/libxml.rb +2 -2
  176. data/lib/active_support/xml_mini/libxmlsax.rb +4 -4
  177. data/lib/active_support/xml_mini/nokogiri.rb +2 -2
  178. data/lib/active_support/xml_mini/nokogirisax.rb +3 -3
  179. data/lib/active_support/xml_mini/rexml.rb +10 -3
  180. data/lib/active_support/xml_mini.rb +2 -10
  181. data/lib/active_support.rb +14 -1
  182. metadata +57 -30
  183. data/lib/active_support/core_ext/array/prepend_and_append.rb +0 -9
  184. data/lib/active_support/core_ext/hash/compact.rb +0 -29
  185. data/lib/active_support/core_ext/hash/transform_values.rb +0 -32
  186. data/lib/active_support/core_ext/kernel/agnostics.rb +0 -13
  187. data/lib/active_support/core_ext/module/reachable.rb +0 -11
  188. data/lib/active_support/core_ext/numeric/inquiry.rb +0 -28
  189. data/lib/active_support/core_ext/range/include_range.rb +0 -25
  190. 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-2020 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/master/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,9 +16,8 @@ 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
23
  def initialize(cache_path, options = nil)
@@ -26,21 +25,26 @@ module ActiveSupport
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,44 +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 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)
34
+ def write_entry(key, entry, **options)
40
35
  if options[:raw] && local_cache
41
36
  raw_entry = Entry.new(entry.value.to_s)
42
37
  raw_entry.expires_at = entry.expires_at
43
- super(key, raw_entry, options)
38
+ super(key, raw_entry, **options)
44
39
  else
45
40
  super
46
41
  end
47
42
  end
48
43
  end
49
44
 
45
+ # Advertise cache versioning support.
46
+ def self.supports_cache_versioning?
47
+ true
48
+ end
49
+
50
50
  prepend Strategy::LocalCache
51
51
  prepend LocalCacheWithRaw
52
52
 
53
53
  ESCAPE_KEY_CHARS = /[\x00-\x20%\x7F-\xFF]/n
54
54
 
55
55
  # Creates a new Dalli::Client instance with specified addresses and options.
56
- # 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)
57
59
  #
58
60
  # ActiveSupport::Cache::MemCacheStore.build_mem_cache
59
- # # => #<Dalli::Client:0x007f98a47d2028 @servers=["localhost:11211"], @options={}, @ring=nil>
61
+ # # => #<Dalli::Client:0x007f98a47d2028 @servers=["127.0.0.1:11211"], @options={}, @ring=nil>
60
62
  # ActiveSupport::Cache::MemCacheStore.build_mem_cache('localhost:10290')
61
63
  # # => #<Dalli::Client:0x007f98a47b3a60 @servers=["localhost:10290"], @options={}, @ring=nil>
62
64
  def self.build_mem_cache(*addresses) # :nodoc:
63
65
  addresses = addresses.flatten
64
66
  options = addresses.extract_options!
65
- addresses = ["localhost:11211"] if addresses.empty?
67
+ addresses = nil if addresses.empty?
66
68
  pool_options = retrieve_pool_options(options)
67
69
 
68
70
  if pool_options.empty?
@@ -79,8 +81,8 @@ module ActiveSupport
79
81
  #
80
82
  # ActiveSupport::Cache::MemCacheStore.new("localhost", "server-downstairs.localnetwork:8229")
81
83
  #
82
- # If no addresses are specified, then MemCacheStore will connect to
83
- # 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).
84
86
  def initialize(*addresses)
85
87
  addresses = addresses.flatten
86
88
  options = addresses.extract_options!
@@ -137,27 +139,28 @@ module ActiveSupport
137
139
 
138
140
  private
139
141
  # Read an entry from the cache.
140
- def read_entry(key, options)
142
+ def read_entry(key, **options)
141
143
  rescue_error_with(nil) { deserialize_entry(@data.with { |c| c.get(key, options) }) }
142
144
  end
143
145
 
144
146
  # Write an entry to the cache.
145
- def write_entry(key, entry, options)
146
- method = options && options[:unless_exist] ? :add : :set
147
- 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)
148
150
  expires_in = options[:expires_in].to_i
149
- if expires_in > 0 && !options[:raw]
151
+ if options[:race_condition_ttl] && expires_in > 0 && !options[:raw]
150
152
  # Set the memcache expire a few minutes in the future to support race condition ttls on read
151
153
  expires_in += 5.minutes
152
154
  end
153
155
  rescue_error_with false do
154
- @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) }
155
158
  end
156
159
  end
157
160
 
158
161
  # Reads multiple entries from the cache implementation.
159
- def read_multi_entries(names, options)
160
- 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) }
161
164
 
162
165
  raw_values = @data.with { |c| c.get_multi(keys_to_names.keys) }
163
166
  values = {}
@@ -174,7 +177,7 @@ module ActiveSupport
174
177
  end
175
178
 
176
179
  # Delete an entry from the cache.
177
- def delete_entry(key, options)
180
+ def delete_entry(key, **options)
178
181
  rescue_error_with(false) { @data.with { |c| c.delete(key) } }
179
182
  end
180
183
 
@@ -189,11 +192,10 @@ module ActiveSupport
189
192
  key
190
193
  end
191
194
 
192
- def deserialize_entry(raw_value)
193
- if raw_value
194
- entry = Marshal.load(raw_value) rescue raw_value
195
- entry.is_a?(Entry) ? entry : Entry.new(entry)
196
- end
195
+ def deserialize_entry(payload)
196
+ entry = super
197
+ entry = Entry.new(entry, compress: false) if entry && !entry.is_a?(Entry)
198
+ entry
197
199
  end
198
200
 
199
201
  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)
@@ -12,6 +12,11 @@ module ActiveSupport
12
12
  class NullStore < Store
13
13
  prepend Strategy::LocalCache
14
14
 
15
+ # Advertise cache versioning support.
16
+ def self.supports_cache_versioning?
17
+ true
18
+ end
19
+
15
20
  def clear(options = nil)
16
21
  end
17
22
 
@@ -28,14 +33,14 @@ module ActiveSupport
28
33
  end
29
34
 
30
35
  private
31
- def read_entry(key, options)
36
+ def read_entry(key, **options)
32
37
  end
33
38
 
34
- def write_entry(key, entry, options)
39
+ def write_entry(key, entry, **options)
35
40
  true
36
41
  end
37
42
 
38
- def delete_entry(key, options)
43
+ def delete_entry(key, **options)
39
44
  false
40
45
  end
41
46
  end