activesupport 5.2.4.4 → 6.1.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 (187) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +353 -435
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +4 -3
  5. data/lib/active_support.rb +14 -1
  6. data/lib/active_support/actionable_error.rb +48 -0
  7. data/lib/active_support/array_inquirer.rb +4 -2
  8. data/lib/active_support/backtrace_cleaner.rb +29 -3
  9. data/lib/active_support/benchmarkable.rb +1 -1
  10. data/lib/active_support/cache.rb +142 -78
  11. data/lib/active_support/cache/file_store.rb +33 -33
  12. data/lib/active_support/cache/mem_cache_store.rb +32 -20
  13. data/lib/active_support/cache/memory_store.rb +59 -33
  14. data/lib/active_support/cache/null_store.rb +8 -3
  15. data/lib/active_support/cache/redis_cache_store.rb +70 -43
  16. data/lib/active_support/cache/strategy/local_cache.rb +41 -26
  17. data/lib/active_support/callbacks.rb +81 -64
  18. data/lib/active_support/concern.rb +70 -3
  19. data/lib/active_support/concurrency/load_interlock_aware_monitor.rb +18 -0
  20. data/lib/active_support/concurrency/share_lock.rb +0 -1
  21. data/lib/active_support/configurable.rb +10 -14
  22. data/lib/active_support/configuration_file.rb +46 -0
  23. data/lib/active_support/core_ext.rb +1 -1
  24. data/lib/active_support/core_ext/array.rb +1 -1
  25. data/lib/active_support/core_ext/array/access.rb +18 -6
  26. data/lib/active_support/core_ext/array/conversions.rb +5 -5
  27. data/lib/active_support/core_ext/array/extract.rb +21 -0
  28. data/lib/active_support/core_ext/benchmark.rb +2 -2
  29. data/lib/active_support/core_ext/class/attribute.rb +32 -47
  30. data/lib/active_support/core_ext/class/subclasses.rb +17 -38
  31. data/lib/active_support/core_ext/date/calculations.rb +6 -5
  32. data/lib/active_support/core_ext/date/conversions.rb +2 -1
  33. data/lib/active_support/core_ext/date_and_time/calculations.rb +37 -47
  34. data/lib/active_support/core_ext/date_and_time/compatibility.rb +15 -0
  35. data/lib/active_support/core_ext/date_and_time/zones.rb +0 -1
  36. data/lib/active_support/core_ext/date_time/calculations.rb +1 -1
  37. data/lib/active_support/core_ext/date_time/conversions.rb +0 -1
  38. data/lib/active_support/core_ext/enumerable.rb +171 -75
  39. data/lib/active_support/core_ext/hash.rb +1 -2
  40. data/lib/active_support/core_ext/hash/conversions.rb +3 -3
  41. data/lib/active_support/core_ext/hash/deep_transform_values.rb +46 -0
  42. data/lib/active_support/core_ext/hash/except.rb +2 -2
  43. data/lib/active_support/core_ext/hash/keys.rb +1 -30
  44. data/lib/active_support/core_ext/hash/slice.rb +6 -27
  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.rb +0 -1
  50. data/lib/active_support/core_ext/module/attr_internal.rb +2 -2
  51. data/lib/active_support/core_ext/module/attribute_accessors.rb +30 -39
  52. data/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb +17 -19
  53. data/lib/active_support/core_ext/module/concerning.rb +8 -2
  54. data/lib/active_support/core_ext/module/delegation.rb +76 -33
  55. data/lib/active_support/core_ext/module/introspection.rb +16 -15
  56. data/lib/active_support/core_ext/module/redefine_method.rb +8 -17
  57. data/lib/active_support/core_ext/name_error.rb +29 -2
  58. data/lib/active_support/core_ext/numeric.rb +0 -1
  59. data/lib/active_support/core_ext/numeric/conversions.rb +129 -129
  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 +14 -2
  64. data/lib/active_support/core_ext/object/try.rb +17 -7
  65. data/lib/active_support/core_ext/object/with_options.rb +1 -1
  66. data/lib/active_support/core_ext/range/compare_range.rb +34 -13
  67. data/lib/active_support/core_ext/range/conversions.rb +31 -29
  68. data/lib/active_support/core_ext/range/each.rb +0 -1
  69. data/lib/active_support/core_ext/range/include_time_with_zone.rb +8 -3
  70. data/lib/active_support/core_ext/regexp.rb +8 -5
  71. data/lib/active_support/core_ext/securerandom.rb +23 -3
  72. data/lib/active_support/core_ext/string/access.rb +5 -16
  73. data/lib/active_support/core_ext/string/conversions.rb +1 -0
  74. data/lib/active_support/core_ext/string/filters.rb +42 -1
  75. data/lib/active_support/core_ext/string/inflections.rb +45 -6
  76. data/lib/active_support/core_ext/string/inquiry.rb +1 -0
  77. data/lib/active_support/core_ext/string/multibyte.rb +6 -5
  78. data/lib/active_support/core_ext/string/output_safety.rb +70 -13
  79. data/lib/active_support/core_ext/string/starts_ends_with.rb +2 -2
  80. data/lib/active_support/core_ext/string/strip.rb +3 -1
  81. data/lib/active_support/core_ext/symbol.rb +3 -0
  82. data/lib/active_support/core_ext/symbol/starts_ends_with.rb +14 -0
  83. data/lib/active_support/core_ext/time/calculations.rb +50 -3
  84. data/lib/active_support/core_ext/time/conversions.rb +2 -0
  85. data/lib/active_support/core_ext/uri.rb +6 -1
  86. data/lib/active_support/current_attributes.rb +15 -2
  87. data/lib/active_support/current_attributes/test_helper.rb +13 -0
  88. data/lib/active_support/dependencies.rb +109 -34
  89. data/lib/active_support/dependencies/zeitwerk_integration.rb +117 -0
  90. data/lib/active_support/deprecation.rb +6 -1
  91. data/lib/active_support/deprecation/behaviors.rb +16 -3
  92. data/lib/active_support/deprecation/disallowed.rb +56 -0
  93. data/lib/active_support/deprecation/instance_delegator.rb +0 -1
  94. data/lib/active_support/deprecation/method_wrappers.rb +18 -23
  95. data/lib/active_support/deprecation/proxy_wrappers.rb +29 -6
  96. data/lib/active_support/deprecation/reporting.rb +50 -7
  97. data/lib/active_support/descendants_tracker.rb +59 -9
  98. data/lib/active_support/duration.rb +90 -38
  99. data/lib/active_support/duration/iso8601_parser.rb +2 -4
  100. data/lib/active_support/duration/iso8601_serializer.rb +18 -14
  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 +1 -0
  106. data/lib/active_support/file_update_checker.rb +0 -1
  107. data/lib/active_support/fork_tracker.rb +62 -0
  108. data/lib/active_support/gem_version.rb +4 -4
  109. data/lib/active_support/hash_with_indifferent_access.rb +64 -41
  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.rb +72 -8
  133. data/lib/active_support/notifications/fanout.rb +116 -16
  134. data/lib/active_support/notifications/instrumenter.rb +71 -9
  135. data/lib/active_support/number_helper.rb +38 -12
  136. data/lib/active_support/number_helper/number_converter.rb +5 -6
  137. data/lib/active_support/number_helper/number_to_currency_converter.rb +4 -9
  138. data/lib/active_support/number_helper/number_to_delimited_converter.rb +3 -2
  139. data/lib/active_support/number_helper/number_to_human_converter.rb +4 -3
  140. data/lib/active_support/number_helper/number_to_human_size_converter.rb +4 -3
  141. data/lib/active_support/number_helper/number_to_percentage_converter.rb +3 -1
  142. data/lib/active_support/number_helper/number_to_phone_converter.rb +2 -1
  143. data/lib/active_support/number_helper/number_to_rounded_converter.rb +8 -7
  144. data/lib/active_support/number_helper/rounding_helper.rb +12 -28
  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 +1 -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.rb +51 -0
  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/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 +32 -17
  172. data/lib/active_support/xml_mini.rb +2 -10
  173. data/lib/active_support/xml_mini/jdom.rb +2 -3
  174. data/lib/active_support/xml_mini/libxml.rb +2 -2
  175. data/lib/active_support/xml_mini/libxmlsax.rb +4 -4
  176. data/lib/active_support/xml_mini/nokogiri.rb +2 -2
  177. data/lib/active_support/xml_mini/nokogirisax.rb +3 -3
  178. data/lib/active_support/xml_mini/rexml.rb +10 -3
  179. metadata +58 -32
  180. data/lib/active_support/core_ext/array/prepend_and_append.rb +0 -9
  181. data/lib/active_support/core_ext/hash/compact.rb +0 -29
  182. data/lib/active_support/core_ext/hash/transform_values.rb +0 -32
  183. data/lib/active_support/core_ext/kernel/agnostics.rb +0 -13
  184. data/lib/active_support/core_ext/module/reachable.rb +0 -11
  185. data/lib/active_support/core_ext/numeric/inquiry.rb +0 -28
  186. data/lib/active_support/core_ext/range/include_range.rb +0 -3
  187. 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
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  #--
4
- # Copyright (c) 2005-2018 David Heinemeier Hansson
4
+ # Copyright (c) 2005-2020 David Heinemeier Hansson
5
5
  #
6
6
  # Permission is hereby granted, free of charge, to any person obtaining
7
7
  # a copy of this software and associated documentation files (the
@@ -34,6 +34,8 @@ module ActiveSupport
34
34
  extend ActiveSupport::Autoload
35
35
 
36
36
  autoload :Concern
37
+ autoload :ActionableError
38
+ autoload :ConfigurationFile
37
39
  autoload :CurrentAttributes
38
40
  autoload :Dependencies
39
41
  autoload :DescendantsTracker
@@ -41,9 +43,11 @@ module ActiveSupport
41
43
  autoload :Executor
42
44
  autoload :FileUpdateChecker
43
45
  autoload :EventedFileUpdateChecker
46
+ autoload :ForkTracker
44
47
  autoload :LogSubscriber
45
48
  autoload :Notifications
46
49
  autoload :Reloader
50
+ autoload :SecureCompareRotator
47
51
 
48
52
  eager_autoload do
49
53
  autoload :BacktraceCleaner
@@ -66,6 +70,7 @@ module ActiveSupport
66
70
  autoload :OrderedHash
67
71
  autoload :OrderedOptions
68
72
  autoload :StringInquirer
73
+ autoload :EnvironmentInquirer
69
74
  autoload :TaggedLogging
70
75
  autoload :XmlMini
71
76
  autoload :ArrayInquirer
@@ -90,6 +95,14 @@ module ActiveSupport
90
95
  def self.to_time_preserves_timezone=(value)
91
96
  DateAndTime::Compatibility.preserve_timezone = value
92
97
  end
98
+
99
+ def self.utc_to_local_returns_utc_offset_times
100
+ DateAndTime::Compatibility.utc_to_local_returns_utc_offset_times
101
+ end
102
+
103
+ def self.utc_to_local_returns_utc_offset_times=(value)
104
+ DateAndTime::Compatibility.utc_to_local_returns_utc_offset_times = value
105
+ end
93
106
  end
94
107
 
95
108
  autoload :I18n, "active_support/i18n"
@@ -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
@@ -3,10 +3,12 @@
3
3
  require "zlib"
4
4
  require "active_support/core_ext/array/extract_options"
5
5
  require "active_support/core_ext/array/wrap"
6
+ require "active_support/core_ext/enumerable"
6
7
  require "active_support/core_ext/module/attribute_accessors"
7
8
  require "active_support/core_ext/numeric/bytes"
8
9
  require "active_support/core_ext/numeric/time"
9
10
  require "active_support/core_ext/object/to_param"
11
+ require "active_support/core_ext/object/try"
10
12
  require "active_support/core_ext/string/inflections"
11
13
 
12
14
  module ActiveSupport
@@ -20,7 +22,7 @@ module ActiveSupport
20
22
 
21
23
  # These options mean something to all cache implementations. Individual cache
22
24
  # implementations may support additional options.
23
- UNIVERSAL_OPTIONS = [:namespace, :compress, :compress_threshold, :expires_in, :race_condition_ttl]
25
+ UNIVERSAL_OPTIONS = [:namespace, :compress, :compress_threshold, :expires_in, :race_condition_ttl, :coder]
24
26
 
25
27
  module Strategy
26
28
  autoload :LocalCache, "active_support/cache/strategy/local_cache"
@@ -52,12 +54,13 @@ module ActiveSupport
52
54
  #
53
55
  # ActiveSupport::Cache.lookup_store(MyOwnCacheStore.new)
54
56
  # # => returns MyOwnCacheStore.new
55
- def lookup_store(*store_option)
56
- store, *parameters = *Array.wrap(store_option).flatten
57
-
57
+ def lookup_store(store = nil, *parameters)
58
58
  case store
59
59
  when Symbol
60
- retrieve_store_class(store).new(*parameters)
60
+ options = parameters.extract_options!
61
+ retrieve_store_class(store).new(*parameters, **options)
62
+ when Array
63
+ lookup_store(*store)
61
64
  when nil
62
65
  ActiveSupport::Cache::MemoryStore.new
63
66
  else
@@ -78,7 +81,7 @@ module ActiveSupport
78
81
  #
79
82
  # The +key+ argument can also respond to +cache_key+ or +to_param+.
80
83
  def expand_cache_key(key, namespace = nil)
81
- expanded_cache_key = (namespace ? "#{namespace}/" : "").dup
84
+ expanded_cache_key = namespace ? +"#{namespace}/" : +""
82
85
 
83
86
  if prefix = ENV["RAILS_CACHE_ID"] || ENV["RAILS_APP_VERSION"]
84
87
  expanded_cache_key << "#{prefix}/"
@@ -155,6 +158,8 @@ module ActiveSupport
155
158
  # threshold is configurable with the <tt>:compress_threshold</tt> option,
156
159
  # specified in bytes.
157
160
  class Store
161
+ DEFAULT_CODER = Marshal
162
+
158
163
  cattr_accessor :logger, instance_writer: true
159
164
 
160
165
  attr_reader :silence, :options
@@ -182,6 +187,7 @@ module ActiveSupport
182
187
  # namespace for the cache.
183
188
  def initialize(options = nil)
184
189
  @options = options ? options.dup : {}
190
+ @coder = @options.delete(:coder) { self.class::DEFAULT_CODER } || NullCoder
185
191
  end
186
192
 
187
193
  # Silences the logger.
@@ -229,6 +235,14 @@ module ActiveSupport
229
235
  # ask whether you should force a cache write. Otherwise, it's clearer to
230
236
  # just call <tt>Cache#write</tt>.
231
237
  #
238
+ # Setting <tt>skip_nil: true</tt> will not cache nil result:
239
+ #
240
+ # cache.fetch('foo') { nil }
241
+ # cache.fetch('bar', skip_nil: true) { nil }
242
+ # cache.exist?('foo') # => true
243
+ # cache.exist?('bar') # => false
244
+ #
245
+ #
232
246
  # Setting <tt>compress: false</tt> disables compression of the cache entry.
233
247
  #
234
248
  # Setting <tt>:expires_in</tt> will set an expiration time on the cache.
@@ -303,14 +317,14 @@ module ActiveSupport
303
317
  # :bar
304
318
  # end
305
319
  # cache.fetch('foo') # => "bar"
306
- def fetch(name, options = nil)
320
+ def fetch(name, options = nil, &block)
307
321
  if block_given?
308
322
  options = merged_options(options)
309
323
  key = normalize_key(name, options)
310
324
 
311
325
  entry = nil
312
326
  instrument(:read, name, options) do |payload|
313
- cached_entry = read_entry(key, options) unless options[:force]
327
+ cached_entry = read_entry(key, **options, event: payload) unless options[:force]
314
328
  entry = handle_expired_entry(cached_entry, key, options)
315
329
  entry = nil if entry && entry.mismatched?(normalize_version(name, options))
316
330
  payload[:super_operation] = :fetch if payload
@@ -320,7 +334,7 @@ module ActiveSupport
320
334
  if entry
321
335
  get_entry_value(entry, name, options)
322
336
  else
323
- save_block_result_to_cache(name, options) { |_name| yield _name }
337
+ save_block_result_to_cache(name, options, &block)
324
338
  end
325
339
  elsif options && options[:force]
326
340
  raise ArgumentError, "Missing block: Calling `Cache#fetch` with `force: true` requires a block."
@@ -333,8 +347,9 @@ module ActiveSupport
333
347
  # the cache with the given key, then that data is returned. Otherwise,
334
348
  # +nil+ is returned.
335
349
  #
336
- # Note, if data was written with the <tt>:expires_in<tt> or <tt>:version</tt> options,
337
- # both of these conditions are applied before the data is returned.
350
+ # Note, if data was written with the <tt>:expires_in</tt> or
351
+ # <tt>:version</tt> options, both of these conditions are applied before
352
+ # the data is returned.
338
353
  #
339
354
  # Options are passed to the underlying cache implementation.
340
355
  def read(name, options = nil)
@@ -343,11 +358,11 @@ module ActiveSupport
343
358
  version = normalize_version(name, options)
344
359
 
345
360
  instrument(:read, name, options) do |payload|
346
- entry = read_entry(key, options)
361
+ entry = read_entry(key, **options, event: payload)
347
362
 
348
363
  if entry
349
364
  if entry.expired?
350
- delete_entry(key, options)
365
+ delete_entry(key, **options)
351
366
  payload[:hit] = false if payload
352
367
  nil
353
368
  elsif entry.mismatched?(version)
@@ -375,7 +390,7 @@ module ActiveSupport
375
390
  options = merged_options(options)
376
391
 
377
392
  instrument :read_multi, names, options do |payload|
378
- read_multi_entries(names, options).tap do |results|
393
+ read_multi_entries(names, **options, event: payload).tap do |results|
379
394
  payload[:hits] = results.keys
380
395
  end
381
396
  end
@@ -387,10 +402,10 @@ module ActiveSupport
387
402
 
388
403
  instrument :write_multi, hash, options do |payload|
389
404
  entries = hash.each_with_object({}) do |(name, value), memo|
390
- memo[normalize_key(name, options)] = Entry.new(value, options.merge(version: normalize_version(name, options)))
405
+ memo[normalize_key(name, options)] = Entry.new(value, **options.merge(version: normalize_version(name, options)))
391
406
  end
392
407
 
393
- write_multi_entries entries, options
408
+ write_multi_entries entries, **options
394
409
  end
395
410
  end
396
411
 
@@ -402,8 +417,6 @@ module ActiveSupport
402
417
  # to the cache. If you do not want to write the cache when the cache is
403
418
  # not found, use #read_multi.
404
419
  #
405
- # Options are passed to the underlying cache implementation.
406
- #
407
420
  # Returns a hash with the data for each of the names. For example:
408
421
  #
409
422
  # cache.write("bim", "bam")
@@ -413,6 +426,17 @@ module ActiveSupport
413
426
  # # => { "bim" => "bam",
414
427
  # # "unknown_key" => "Fallback value for key: unknown_key" }
415
428
  #
429
+ # Options are passed to the underlying cache implementation. For example:
430
+ #
431
+ # cache.fetch_multi("fizz", expires_in: 5.seconds) do |key|
432
+ # "buzz"
433
+ # end
434
+ # # => {"fizz"=>"buzz"}
435
+ # cache.read("fizz")
436
+ # # => "buzz"
437
+ # sleep(6)
438
+ # cache.read("fizz")
439
+ # # => nil
416
440
  def fetch_multi(*names)
417
441
  raise ArgumentError, "Missing block: `Cache#fetch_multi` requires a block." unless block_given?
418
442
 
@@ -420,18 +444,18 @@ module ActiveSupport
420
444
  options = merged_options(options)
421
445
 
422
446
  instrument :read_multi, names, options do |payload|
423
- read_multi_entries(names, options).tap do |results|
424
- payload[:hits] = results.keys
425
- payload[:super_operation] = :fetch_multi
447
+ reads = read_multi_entries(names, **options)
448
+ writes = {}
449
+ ordered = names.index_with do |name|
450
+ reads.fetch(name) { writes[name] = yield(name) }
451
+ end
426
452
 
427
- writes = {}
453
+ payload[:hits] = reads.keys
454
+ payload[:super_operation] = :fetch_multi
428
455
 
429
- (names - results.keys).each do |name|
430
- results[name] = writes[name] = yield(name)
431
- end
456
+ write_multi(writes, options)
432
457
 
433
- write_multi writes, options
434
- end
458
+ ordered
435
459
  end
436
460
  end
437
461
 
@@ -442,8 +466,8 @@ module ActiveSupport
442
466
  options = merged_options(options)
443
467
 
444
468
  instrument(:write, name, options) do
445
- entry = Entry.new(value, options.merge(version: normalize_version(name, options)))
446
- write_entry(normalize_key(name, options), entry, options)
469
+ entry = Entry.new(value, **options.merge(version: normalize_version(name, options)))
470
+ write_entry(normalize_key(name, options), entry, **options)
447
471
  end
448
472
  end
449
473
 
@@ -454,7 +478,19 @@ module ActiveSupport
454
478
  options = merged_options(options)
455
479
 
456
480
  instrument(:delete, name) do
457
- delete_entry(normalize_key(name, options), options)
481
+ delete_entry(normalize_key(name, options), **options)
482
+ end
483
+ end
484
+
485
+ # Deletes multiple entries in the cache.
486
+ #
487
+ # Options are passed to the underlying cache implementation.
488
+ def delete_multi(names, options = nil)
489
+ options = merged_options(options)
490
+ names.map! { |key| normalize_key(key, options) }
491
+
492
+ instrument :delete_multi, names do
493
+ delete_multi_entries(names, **options)
458
494
  end
459
495
  end
460
496
 
@@ -464,8 +500,8 @@ module ActiveSupport
464
500
  def exist?(name, options = nil)
465
501
  options = merged_options(options)
466
502
 
467
- instrument(:exist?, name) do
468
- entry = read_entry(normalize_key(name, options), options)
503
+ instrument(:exist?, name) do |payload|
504
+ entry = read_entry(normalize_key(name, options), **options, event: payload)
469
505
  (entry && !entry.expired? && !entry.mismatched?(normalize_version(name, options))) || false
470
506
  end
471
507
  end
@@ -474,7 +510,7 @@ module ActiveSupport
474
510
  #
475
511
  # Options are passed to the underlying cache implementation.
476
512
  #
477
- # All implementations may not support this method.
513
+ # Some implementations may not support this method.
478
514
  def delete_matched(matcher, options = nil)
479
515
  raise NotImplementedError.new("#{self.class.name} does not support delete_matched")
480
516
  end
@@ -483,7 +519,7 @@ module ActiveSupport
483
519
  #
484
520
  # Options are passed to the underlying cache implementation.
485
521
  #
486
- # All implementations may not support this method.
522
+ # Some implementations may not support this method.
487
523
  def increment(name, amount = 1, options = nil)
488
524
  raise NotImplementedError.new("#{self.class.name} does not support increment")
489
525
  end
@@ -492,7 +528,7 @@ module ActiveSupport
492
528
  #
493
529
  # Options are passed to the underlying cache implementation.
494
530
  #
495
- # All implementations may not support this method.
531
+ # Some implementations may not support this method.
496
532
  def decrement(name, amount = 1, options = nil)
497
533
  raise NotImplementedError.new("#{self.class.name} does not support decrement")
498
534
  end
@@ -501,7 +537,7 @@ module ActiveSupport
501
537
  #
502
538
  # Options are passed to the underlying cache implementation.
503
539
  #
504
- # All implementations may not support this method.
540
+ # Some implementations may not support this method.
505
541
  def cleanup(options = nil)
506
542
  raise NotImplementedError.new("#{self.class.name} does not support cleanup")
507
543
  end
@@ -511,7 +547,7 @@ module ActiveSupport
511
547
  #
512
548
  # The options hash is passed to the underlying cache implementation.
513
549
  #
514
- # All implementations may not support this method.
550
+ # Some implementations may not support this method.
515
551
  def clear(options = nil)
516
552
  raise NotImplementedError.new("#{self.class.name} does not support clear")
517
553
  end
@@ -538,58 +574,73 @@ module ActiveSupport
538
574
 
539
575
  # Reads an entry from the cache implementation. Subclasses must implement
540
576
  # this method.
541
- def read_entry(key, options)
577
+ def read_entry(key, **options)
542
578
  raise NotImplementedError.new
543
579
  end
544
580
 
545
581
  # Writes an entry to the cache implementation. Subclasses must implement
546
582
  # this method.
547
- def write_entry(key, entry, options)
583
+ def write_entry(key, entry, **options)
548
584
  raise NotImplementedError.new
549
585
  end
550
586
 
587
+ def serialize_entry(entry)
588
+ @coder.dump(entry)
589
+ end
590
+
591
+ def deserialize_entry(payload)
592
+ payload.nil? ? nil : @coder.load(payload)
593
+ end
594
+
551
595
  # Reads multiple entries from the cache implementation. Subclasses MAY
552
596
  # implement this method.
553
- def read_multi_entries(names, options)
554
- results = {}
555
- names.each do |name|
556
- key = normalize_key(name, options)
597
+ def read_multi_entries(names, **options)
598
+ names.each_with_object({}) do |name, results|
599
+ key = normalize_key(name, options)
600
+ entry = read_entry(key, **options)
601
+
602
+ next unless entry
603
+
557
604
  version = normalize_version(name, options)
558
- entry = read_entry(key, options)
559
-
560
- if entry
561
- if entry.expired?
562
- delete_entry(key, options)
563
- elsif entry.mismatched?(version)
564
- # Skip mismatched versions
565
- else
566
- results[name] = entry.value
567
- end
605
+
606
+ if entry.expired?
607
+ delete_entry(key, **options)
608
+ elsif !entry.mismatched?(version)
609
+ results[name] = entry.value
568
610
  end
569
611
  end
570
- results
571
612
  end
572
613
 
573
614
  # Writes multiple entries to the cache implementation. Subclasses MAY
574
615
  # implement this method.
575
- def write_multi_entries(hash, options)
616
+ def write_multi_entries(hash, **options)
576
617
  hash.each do |key, entry|
577
- write_entry key, entry, options
618
+ write_entry key, entry, **options
578
619
  end
579
620
  end
580
621
 
581
622
  # Deletes an entry from the cache implementation. Subclasses must
582
623
  # implement this method.
583
- def delete_entry(key, options)
624
+ def delete_entry(key, **options)
584
625
  raise NotImplementedError.new
585
626
  end
586
627
 
628
+ # Deletes multiples entries in the cache implementation. Subclasses MAY
629
+ # implement this method.
630
+ def delete_multi_entries(entries, **options)
631
+ entries.count { |key| delete_entry(key, **options) }
632
+ end
633
+
587
634
  # Merges the default options with ones specific to a method call.
588
635
  def merged_options(call_options)
589
636
  if call_options
590
- options.merge(call_options)
637
+ if options.empty?
638
+ call_options
639
+ else
640
+ options.merge(call_options)
641
+ end
591
642
  else
592
- options.dup
643
+ options
593
644
  end
594
645
  end
595
646
 
@@ -616,6 +667,10 @@ module ActiveSupport
616
667
  namespace = namespace.call
617
668
  end
618
669
 
670
+ if key && key.encoding != Encoding::UTF_8
671
+ key = key.dup.force_encoding(Encoding::UTF_8)
672
+ end
673
+
619
674
  if namespace
620
675
  "#{namespace}:#{key}"
621
676
  else
@@ -632,15 +687,15 @@ module ActiveSupport
632
687
  case key
633
688
  when Array
634
689
  if key.size > 1
635
- key = key.collect { |element| expanded_key(element) }
690
+ key.collect { |element| expanded_key(element) }
636
691
  else
637
- key = key.first
692
+ expanded_key(key.first)
638
693
  end
639
694
  when Hash
640
- key = key.sort_by { |k, _| k.to_s }.collect { |k, v| "#{k}=#{v}" }
641
- end
642
-
643
- key.to_param
695
+ key.collect { |k, v| "#{k}=#{v}" }.sort!
696
+ else
697
+ key
698
+ end.to_param
644
699
  end
645
700
 
646
701
  def normalize_version(key, options = nil)
@@ -650,24 +705,21 @@ module ActiveSupport
650
705
  def expanded_version(key)
651
706
  case
652
707
  when key.respond_to?(:cache_version) then key.cache_version.to_param
653
- when key.is_a?(Array) then key.map { |element| expanded_version(element) }.compact.to_param
708
+ when key.is_a?(Array) then key.map { |element| expanded_version(element) }.tap(&:compact!).to_param
654
709
  when key.respond_to?(:to_a) then expanded_version(key.to_a)
655
710
  end
656
711
  end
657
712
 
658
713
  def instrument(operation, key, options = nil)
659
- log { "Cache #{operation}: #{normalize_key(key, options)}#{options.blank? ? "" : " (#{options.inspect})"}" }
714
+ if logger && logger.debug? && !silence?
715
+ logger.debug "Cache #{operation}: #{normalize_key(key, options)}#{options.blank? ? "" : " (#{options.inspect})"}"
716
+ end
660
717
 
661
- payload = { key: key }
718
+ payload = { key: key, store: self.class.name }
662
719
  payload.merge!(options) if options.is_a?(Hash)
663
720
  ActiveSupport::Notifications.instrument("cache_#{operation}.active_support", payload) { yield(payload) }
664
721
  end
665
722
 
666
- def log
667
- return unless logger && logger.debug? && !silence?
668
- logger.debug(yield)
669
- end
670
-
671
723
  def handle_expired_entry(entry, key, options)
672
724
  if entry && entry.expired?
673
725
  race_ttl = options[:race_condition_ttl].to_i
@@ -677,7 +729,7 @@ module ActiveSupport
677
729
  entry.expires_at = Time.now + race_ttl
678
730
  write_entry(key, entry, expires_in: race_ttl * 2)
679
731
  else
680
- delete_entry(key, options)
732
+ delete_entry(key, **options)
681
733
  end
682
734
  entry = nil
683
735
  end
@@ -685,7 +737,7 @@ module ActiveSupport
685
737
  end
686
738
 
687
739
  def get_entry_value(entry, name, options)
688
- instrument(:fetch_hit, name, options) {}
740
+ instrument(:fetch_hit, name, options) { }
689
741
  entry.value
690
742
  end
691
743
 
@@ -694,11 +746,23 @@ module ActiveSupport
694
746
  yield(name)
695
747
  end
696
748
 
697
- write(name, result, options)
749
+ write(name, result, options) unless result.nil? && options[:skip_nil]
698
750
  result
699
751
  end
700
752
  end
701
753
 
754
+ module NullCoder # :nodoc:
755
+ class << self
756
+ def load(payload)
757
+ payload
758
+ end
759
+
760
+ def dump(entry)
761
+ entry
762
+ end
763
+ end
764
+ end
765
+
702
766
  # This class is used to represent cache entries. Cache entries have a value, an optional
703
767
  # expiration time, and an optional version. The expiration time is used to support the :race_condition_ttl option
704
768
  # on the cache. The version is used to support the :version option on the cache for rejecting
@@ -749,8 +813,8 @@ module ActiveSupport
749
813
  end
750
814
 
751
815
  # Returns the size of the cached value. This could be less than
752
- # <tt>value.size</tt> if the data is compressed.
753
- def size
816
+ # <tt>value.bytesize</tt> if the data is compressed.
817
+ def bytesize
754
818
  case value
755
819
  when NilClass
756
820
  0