activesupport 5.2.6 → 6.0.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of activesupport might be problematic. Click here for more details.

Files changed (138) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +327 -433
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +3 -2
  5. data/lib/active_support/actionable_error.rb +48 -0
  6. data/lib/active_support/backtrace_cleaner.rb +28 -1
  7. data/lib/active_support/cache/file_store.rb +22 -22
  8. data/lib/active_support/cache/mem_cache_store.rb +16 -2
  9. data/lib/active_support/cache/memory_store.rb +7 -2
  10. data/lib/active_support/cache/null_store.rb +5 -0
  11. data/lib/active_support/cache/redis_cache_store.rb +47 -25
  12. data/lib/active_support/cache.rb +45 -23
  13. data/lib/active_support/callbacks.rb +16 -5
  14. data/lib/active_support/concern.rb +24 -1
  15. data/lib/active_support/configurable.rb +7 -11
  16. data/lib/active_support/core_ext/array/access.rb +18 -6
  17. data/lib/active_support/core_ext/array/extract.rb +21 -0
  18. data/lib/active_support/core_ext/array/prepend_and_append.rb +2 -6
  19. data/lib/active_support/core_ext/array.rb +1 -1
  20. data/lib/active_support/core_ext/class/attribute.rb +11 -16
  21. data/lib/active_support/core_ext/class/subclasses.rb +1 -1
  22. data/lib/active_support/core_ext/date/calculations.rb +6 -5
  23. data/lib/active_support/core_ext/date_and_time/calculations.rb +24 -47
  24. data/lib/active_support/core_ext/date_time/calculations.rb +1 -1
  25. data/lib/active_support/core_ext/enumerable.rb +97 -73
  26. data/lib/active_support/core_ext/hash/compact.rb +2 -26
  27. data/lib/active_support/core_ext/hash/deep_transform_values.rb +46 -0
  28. data/lib/active_support/core_ext/hash/except.rb +1 -1
  29. data/lib/active_support/core_ext/hash/keys.rb +0 -29
  30. data/lib/active_support/core_ext/hash/slice.rb +3 -25
  31. data/lib/active_support/core_ext/hash/transform_values.rb +2 -29
  32. data/lib/active_support/core_ext/hash.rb +1 -2
  33. data/lib/active_support/core_ext/integer/multiple.rb +1 -1
  34. data/lib/active_support/core_ext/kernel.rb +0 -1
  35. data/lib/active_support/core_ext/load_error.rb +1 -1
  36. data/lib/active_support/core_ext/module/attribute_accessors.rb +7 -10
  37. data/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb +13 -19
  38. data/lib/active_support/core_ext/module/delegation.rb +33 -7
  39. data/lib/active_support/core_ext/module/introspection.rb +37 -13
  40. data/lib/active_support/core_ext/module/reachable.rb +1 -6
  41. data/lib/active_support/core_ext/module/redefine_method.rb +8 -17
  42. data/lib/active_support/core_ext/module.rb +0 -1
  43. data/lib/active_support/core_ext/numeric/conversions.rb +124 -128
  44. data/lib/active_support/core_ext/numeric/inquiry.rb +2 -25
  45. data/lib/active_support/core_ext/numeric.rb +0 -1
  46. data/lib/active_support/core_ext/object/blank.rb +1 -2
  47. data/lib/active_support/core_ext/object/duplicable.rb +7 -114
  48. data/lib/active_support/core_ext/object/json.rb +1 -0
  49. data/lib/active_support/core_ext/object/try.rb +15 -7
  50. data/lib/active_support/core_ext/object/with_options.rb +1 -1
  51. data/lib/active_support/core_ext/range/compare_range.rb +22 -13
  52. data/lib/active_support/core_ext/range/conversions.rb +31 -29
  53. data/lib/active_support/core_ext/range/include_range.rb +6 -0
  54. data/lib/active_support/core_ext/regexp.rb +0 -4
  55. data/lib/active_support/core_ext/securerandom.rb +23 -3
  56. data/lib/active_support/core_ext/string/access.rb +8 -0
  57. data/lib/active_support/core_ext/string/filters.rb +42 -1
  58. data/lib/active_support/core_ext/string/inflections.rb +7 -2
  59. data/lib/active_support/core_ext/string/multibyte.rb +4 -3
  60. data/lib/active_support/core_ext/string/output_safety.rb +61 -5
  61. data/lib/active_support/core_ext/string/strip.rb +3 -1
  62. data/lib/active_support/core_ext/time/calculations.rb +31 -2
  63. data/lib/active_support/core_ext/uri.rb +1 -0
  64. data/lib/active_support/current_attributes.rb +8 -0
  65. data/lib/active_support/dependencies/zeitwerk_integration.rb +110 -0
  66. data/lib/active_support/dependencies.rb +69 -16
  67. data/lib/active_support/deprecation/behaviors.rb +1 -1
  68. data/lib/active_support/deprecation/method_wrappers.rb +8 -20
  69. data/lib/active_support/deprecation/proxy_wrappers.rb +24 -5
  70. data/lib/active_support/deprecation.rb +1 -1
  71. data/lib/active_support/descendants_tracker.rb +56 -9
  72. data/lib/active_support/duration/iso8601_parser.rb +2 -3
  73. data/lib/active_support/duration/iso8601_serializer.rb +3 -4
  74. data/lib/active_support/duration.rb +12 -15
  75. data/lib/active_support/encrypted_configuration.rb +0 -4
  76. data/lib/active_support/encrypted_file.rb +2 -1
  77. data/lib/active_support/evented_file_update_checker.rb +39 -9
  78. data/lib/active_support/execution_wrapper.rb +1 -0
  79. data/lib/active_support/gem_version.rb +3 -3
  80. data/lib/active_support/hash_with_indifferent_access.rb +22 -18
  81. data/lib/active_support/i18n.rb +1 -0
  82. data/lib/active_support/i18n_railtie.rb +9 -1
  83. data/lib/active_support/inflector/inflections.rb +1 -4
  84. data/lib/active_support/inflector/methods.rb +15 -27
  85. data/lib/active_support/inflector/transliterate.rb +47 -18
  86. data/lib/active_support/json/decoding.rb +23 -23
  87. data/lib/active_support/json/encoding.rb +6 -2
  88. data/lib/active_support/key_generator.rb +0 -32
  89. data/lib/active_support/lazy_load_hooks.rb +5 -1
  90. data/lib/active_support/locale/en.rb +31 -0
  91. data/lib/active_support/log_subscriber.rb +31 -8
  92. data/lib/active_support/logger.rb +0 -15
  93. data/lib/active_support/logger_silence.rb +28 -12
  94. data/lib/active_support/logger_thread_safe_level.rb +26 -4
  95. data/lib/active_support/message_encryptor.rb +3 -5
  96. data/lib/active_support/message_verifier.rb +3 -3
  97. data/lib/active_support/multibyte/chars.rb +29 -48
  98. data/lib/active_support/multibyte/unicode.rb +44 -281
  99. data/lib/active_support/notifications/fanout.rb +98 -13
  100. data/lib/active_support/notifications/instrumenter.rb +79 -8
  101. data/lib/active_support/notifications.rb +41 -4
  102. data/lib/active_support/number_helper/number_to_currency_converter.rb +2 -2
  103. data/lib/active_support/number_helper/number_to_delimited_converter.rb +3 -1
  104. data/lib/active_support/number_helper/number_to_human_converter.rb +3 -1
  105. data/lib/active_support/number_helper/number_to_human_size_converter.rb +3 -1
  106. data/lib/active_support/number_helper/number_to_percentage_converter.rb +3 -1
  107. data/lib/active_support/number_helper/number_to_phone_converter.rb +2 -0
  108. data/lib/active_support/number_helper/number_to_rounded_converter.rb +5 -3
  109. data/lib/active_support/number_helper.rb +7 -0
  110. data/lib/active_support/ordered_options.rb +1 -1
  111. data/lib/active_support/parameter_filter.rb +129 -0
  112. data/lib/active_support/rails.rb +0 -6
  113. data/lib/active_support/reloader.rb +4 -5
  114. data/lib/active_support/security_utils.rb +1 -1
  115. data/lib/active_support/subscriber.rb +65 -26
  116. data/lib/active_support/tagged_logging.rb +13 -4
  117. data/lib/active_support/test_case.rb +91 -0
  118. data/lib/active_support/testing/assertions.rb +15 -1
  119. data/lib/active_support/testing/deprecation.rb +0 -1
  120. data/lib/active_support/testing/file_fixtures.rb +2 -0
  121. data/lib/active_support/testing/isolation.rb +2 -2
  122. data/lib/active_support/testing/method_call_assertions.rb +28 -1
  123. data/lib/active_support/testing/parallelization.rb +128 -0
  124. data/lib/active_support/testing/stream.rb +1 -1
  125. data/lib/active_support/testing/time_helpers.rb +7 -7
  126. data/lib/active_support/time_with_zone.rb +15 -5
  127. data/lib/active_support/values/time_zone.rb +12 -7
  128. data/lib/active_support/xml_mini/jdom.rb +2 -2
  129. data/lib/active_support/xml_mini/libxml.rb +2 -2
  130. data/lib/active_support/xml_mini/libxmlsax.rb +4 -4
  131. data/lib/active_support/xml_mini/nokogiri.rb +2 -2
  132. data/lib/active_support/xml_mini/nokogirisax.rb +3 -3
  133. data/lib/active_support/xml_mini/rexml.rb +2 -2
  134. data/lib/active_support/xml_mini.rb +2 -9
  135. data/lib/active_support.rb +2 -1
  136. metadata +34 -9
  137. data/lib/active_support/core_ext/kernel/agnostics.rb +0 -13
  138. 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-2019 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
 
@@ -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
@@ -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
@@ -82,6 +85,26 @@ module ActiveSupport
82
85
  end
83
86
 
84
87
  private
88
+
89
+ FORMATTED_GEMS_PATTERN = /\A[^\/]+ \([\w.]+\) /
90
+
91
+ def add_gem_filter
92
+ gems_paths = (Gem.path | [Gem.default_dir]).map { |p| Regexp.escape(p) }
93
+ return if gems_paths.empty?
94
+
95
+ gems_regexp = %r{(#{gems_paths.join('|')})/(bundler/)?gems/([^/]+)-([\w.]+)/(.*)}
96
+ gems_result = '\3 (\4) \5'
97
+ add_filter { |line| line.sub(gems_regexp, gems_result) }
98
+ end
99
+
100
+ def add_gem_silencer
101
+ add_silencer { |line| FORMATTED_GEMS_PATTERN.match?(line) }
102
+ end
103
+
104
+ def add_stdlib_silencer
105
+ add_silencer { |line| line.start_with?(RbConfig::CONFIG["rubylibdir"]) }
106
+ end
107
+
85
108
  def filter_backtrace(backtrace)
86
109
  @filters.each do |f|
87
110
  backtrace = backtrace.map { |line| f.call(line) }
@@ -99,7 +122,11 @@ module ActiveSupport
99
122
  end
100
123
 
101
124
  def noise(backtrace)
102
- backtrace - silence(backtrace)
125
+ backtrace.select do |line|
126
+ @silencers.any? do |s|
127
+ s.call(line)
128
+ end
129
+ end
103
130
  end
104
131
  end
105
132
  end
@@ -18,7 +18,6 @@ module ActiveSupport
18
18
  DIR_FORMATTER = "%03X"
19
19
  FILENAME_MAX_SIZE = 228 # max filename size on file system is 255, minus room for timestamp and random characters appended by Tempfile (used by atomic write)
20
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,11 +25,16 @@ 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
39
  rescue Errno::ENOENT
36
40
  end
@@ -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
@@ -28,6 +28,14 @@ module ActiveSupport
28
28
  # Provide support for raw values in the local cache strategy.
29
29
  module LocalCacheWithRaw # :nodoc:
30
30
  private
31
+ def read_entry(key, options)
32
+ entry = super
33
+ if options[:raw] && local_cache && entry
34
+ entry = deserialize_entry(entry.value)
35
+ end
36
+ entry
37
+ end
38
+
31
39
  def write_entry(key, entry, options)
32
40
  if options[:raw] && local_cache
33
41
  raw_entry = Entry.new(entry.value.to_s)
@@ -39,6 +47,11 @@ module ActiveSupport
39
47
  end
40
48
  end
41
49
 
50
+ # Advertise cache versioning support.
51
+ def self.supports_cache_versioning?
52
+ true
53
+ end
54
+
42
55
  prepend Strategy::LocalCache
43
56
  prepend LocalCacheWithRaw
44
57
 
@@ -181,8 +194,9 @@ module ActiveSupport
181
194
  key
182
195
  end
183
196
 
184
- def deserialize_entry(entry)
185
- if entry
197
+ def deserialize_entry(raw_value)
198
+ if raw_value
199
+ entry = Marshal.load(raw_value) rescue raw_value
186
200
  entry.is_a?(Entry) ? entry : Entry.new(entry)
187
201
  end
188
202
  end
@@ -30,6 +30,11 @@ module ActiveSupport
30
30
  @pruning = false
31
31
  end
32
32
 
33
+ # Advertise cache versioning support.
34
+ def self.supports_cache_versioning?
35
+ true
36
+ end
37
+
33
38
  # Delete all data stored in a given cache store.
34
39
  def clear(options = nil)
35
40
  synchronize do
@@ -57,13 +62,13 @@ module ActiveSupport
57
62
  return if pruning?
58
63
  @pruning = true
59
64
  begin
60
- start_time = Time.now
65
+ start_time = Concurrent.monotonic_time
61
66
  cleanup
62
67
  instrument(:prune, target_size, from: @cache_size) do
63
68
  keys = synchronize { @key_access.keys.sort { |a, b| @key_access[a].to_f <=> @key_access[b].to_f } }
64
69
  keys.each do |key|
65
70
  delete_entry(key, options)
66
- return if @cache_size <= target_size || (max_time && Time.now - start_time > max_time)
71
+ return if @cache_size <= target_size || (max_time && Concurrent.monotonic_time - start_time > max_time)
67
72
  end
68
73
  end
69
74
  ensure
@@ -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
 
@@ -17,7 +17,6 @@ end
17
17
 
18
18
  require "digest/sha2"
19
19
  require "active_support/core_ext/marshal"
20
- require "active_support/core_ext/hash/transform_values"
21
20
 
22
21
  module ActiveSupport
23
22
  module Cache
@@ -67,9 +66,22 @@ module ActiveSupport
67
66
  SCAN_BATCH_SIZE = 1000
68
67
  private_constant :SCAN_BATCH_SIZE
69
68
 
69
+ # Advertise cache versioning support.
70
+ def self.supports_cache_versioning?
71
+ true
72
+ end
73
+
70
74
  # Support raw values in the local cache strategy.
71
75
  module LocalCacheWithRaw # :nodoc:
72
76
  private
77
+ def read_entry(key, options)
78
+ entry = super
79
+ if options[:raw] && local_cache && entry
80
+ entry = deserialize_entry(entry.value)
81
+ end
82
+ entry
83
+ end
84
+
73
85
  def write_entry(key, entry, options)
74
86
  if options[:raw] && local_cache
75
87
  raw_entry = Entry.new(serialize_entry(entry, raw: true))
@@ -140,15 +152,17 @@ module ActiveSupport
140
152
 
141
153
  # Creates a new Redis cache store.
142
154
  #
143
- # Handles three options: block provided to instantiate, single URL
144
- # provided, and multiple URLs provided.
155
+ # Handles four options: :redis block, :redis instance, single :url
156
+ # string, and multiple :url strings.
145
157
  #
146
- # :redis Proc -> options[:redis].call
147
- # :url String -> Redis.new(url: …)
148
- # :url Array -> Redis::Distributed.new([{ url: … }, { url: … }, …])
158
+ # Option Class Result
159
+ # :redis Proc -> options[:redis].call
160
+ # :redis Object -> options[:redis]
161
+ # :url String -> Redis.new(url: …)
162
+ # :url Array -> Redis::Distributed.new([{ url: … }, { url: … }, …])
149
163
  #
150
164
  # No namespace is set by default. Provide one if the Redis cache
151
- # server is shared with other apps: <tt>namespace: 'myapp-cache'<tt>.
165
+ # server is shared with other apps: <tt>namespace: 'myapp-cache'</tt>.
152
166
  #
153
167
  # Compression is enabled by default with a 1kB threshold, so cached
154
168
  # values larger than 1kB are automatically compressed. Disable by
@@ -251,7 +265,14 @@ module ActiveSupport
251
265
  def increment(name, amount = 1, options = nil)
252
266
  instrument :increment, name, amount: amount do
253
267
  failsafe :increment do
254
- redis.with { |c| c.incrby normalize_key(name, options), amount }
268
+ options = merged_options(options)
269
+ key = normalize_key(name, options)
270
+
271
+ redis.with do |c|
272
+ c.incrby(key, amount).tap do
273
+ write_key_expiry(c, key, options)
274
+ end
275
+ end
255
276
  end
256
277
  end
257
278
  end
@@ -267,7 +288,14 @@ module ActiveSupport
267
288
  def decrement(name, amount = 1, options = nil)
268
289
  instrument :decrement, name, amount: amount do
269
290
  failsafe :decrement do
270
- redis.with { |c| c.decrby normalize_key(name, options), amount }
291
+ options = merged_options(options)
292
+ key = normalize_key(name, options)
293
+
294
+ redis.with do |c|
295
+ c.decrby(key, amount).tap do
296
+ write_key_expiry(c, key, options)
297
+ end
298
+ end
271
299
  end
272
300
  end
273
301
  end
@@ -320,8 +348,7 @@ module ActiveSupport
320
348
  # Read an entry from the cache.
321
349
  def read_entry(key, options = nil)
322
350
  failsafe :read_entry do
323
- raw = options && options.fetch(:raw, false)
324
- deserialize_entry(redis.with { |c| c.get(key) }, raw: raw)
351
+ deserialize_entry redis.with { |c| c.get(key) }
325
352
  end
326
353
  end
327
354
 
@@ -336,7 +363,7 @@ module ActiveSupport
336
363
  def read_multi_mget(*names)
337
364
  options = names.extract_options!
338
365
  options = merged_options(options)
339
- raw = options && options.fetch(:raw, false)
366
+ return {} if names == []
340
367
 
341
368
  keys = names.map { |name| normalize_key(name, options) }
342
369
 
@@ -346,7 +373,7 @@ module ActiveSupport
346
373
 
347
374
  names.zip(values).each_with_object({}) do |(name, value), results|
348
375
  if value
349
- entry = deserialize_entry(value, raw: raw)
376
+ entry = deserialize_entry(value)
350
377
  unless entry.nil? || entry.expired? || entry.mismatched?(normalize_version(name, options))
351
378
  results[name] = entry.value
352
379
  end
@@ -380,6 +407,12 @@ module ActiveSupport
380
407
  end
381
408
  end
382
409
 
410
+ def write_key_expiry(client, key, options)
411
+ if options[:expires_in] && client.ttl(key).negative?
412
+ client.expire key, options[:expires_in].to_i
413
+ end
414
+ end
415
+
383
416
  # Delete an entry from the cache.
384
417
  def delete_entry(key, options)
385
418
  failsafe :delete_entry, returning: false do
@@ -415,20 +448,9 @@ module ActiveSupport
415
448
  end
416
449
  end
417
450
 
418
- def deserialize_entry(serialized_entry, raw:)
451
+ def deserialize_entry(serialized_entry)
419
452
  if serialized_entry
420
453
  entry = Marshal.load(serialized_entry) rescue serialized_entry
421
-
422
- written_raw = serialized_entry.equal?(entry)
423
- if raw != written_raw
424
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
425
- Using a different value for the raw option when reading and writing
426
- to a cache key is deprecated for :redis_cache_store and Rails 6.0
427
- will stop automatically detecting the format when reading to avoid
428
- marshal loading untrusted raw strings.
429
- MSG
430
- end
431
-
432
454
  entry.is_a?(Entry) ? entry : Entry.new(entry)
433
455
  end
434
456
  end
@@ -229,6 +229,14 @@ module ActiveSupport
229
229
  # ask whether you should force a cache write. Otherwise, it's clearer to
230
230
  # just call <tt>Cache#write</tt>.
231
231
  #
232
+ # Setting <tt>skip_nil: true</tt> will not cache nil result:
233
+ #
234
+ # cache.fetch('foo') { nil }
235
+ # cache.fetch('bar', skip_nil: true) { nil }
236
+ # cache.exist?('foo') # => true
237
+ # cache.exist?('bar') # => false
238
+ #
239
+ #
232
240
  # Setting <tt>compress: false</tt> disables compression of the cache entry.
233
241
  #
234
242
  # Setting <tt>:expires_in</tt> will set an expiration time on the cache.
@@ -333,8 +341,9 @@ module ActiveSupport
333
341
  # the cache with the given key, then that data is returned. Otherwise,
334
342
  # +nil+ is returned.
335
343
  #
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.
344
+ # Note, if data was written with the <tt>:expires_in</tt> or
345
+ # <tt>:version</tt> options, both of these conditions are applied before
346
+ # the data is returned.
338
347
  #
339
348
  # Options are passed to the underlying cache implementation.
340
349
  def read(name, options = nil)
@@ -402,8 +411,6 @@ module ActiveSupport
402
411
  # to the cache. If you do not want to write the cache when the cache is
403
412
  # not found, use #read_multi.
404
413
  #
405
- # Options are passed to the underlying cache implementation.
406
- #
407
414
  # Returns a hash with the data for each of the names. For example:
408
415
  #
409
416
  # cache.write("bim", "bam")
@@ -413,6 +420,17 @@ module ActiveSupport
413
420
  # # => { "bim" => "bam",
414
421
  # # "unknown_key" => "Fallback value for key: unknown_key" }
415
422
  #
423
+ # Options are passed to the underlying cache implementation. For example:
424
+ #
425
+ # cache.fetch_multi("fizz", expires_in: 5.seconds) do |key|
426
+ # "buzz"
427
+ # end
428
+ # # => {"fizz"=>"buzz"}
429
+ # cache.read("fizz")
430
+ # # => "buzz"
431
+ # sleep(6)
432
+ # cache.read("fizz")
433
+ # # => nil
416
434
  def fetch_multi(*names)
417
435
  raise ArgumentError, "Missing block: `Cache#fetch_multi` requires a block." unless block_given?
418
436
 
@@ -420,18 +438,18 @@ module ActiveSupport
420
438
  options = merged_options(options)
421
439
 
422
440
  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
441
+ reads = read_multi_entries(names, options)
442
+ writes = {}
443
+ ordered = names.each_with_object({}) do |name, hash|
444
+ hash[name] = reads.fetch(name) { writes[name] = yield(name) }
445
+ end
426
446
 
427
- writes = {}
447
+ payload[:hits] = reads.keys
448
+ payload[:super_operation] = :fetch_multi
428
449
 
429
- (names - results.keys).each do |name|
430
- results[name] = writes[name] = yield(name)
431
- end
450
+ write_multi(writes, options)
432
451
 
433
- write_multi writes, options
434
- end
452
+ ordered
435
453
  end
436
454
  end
437
455
 
@@ -474,7 +492,7 @@ module ActiveSupport
474
492
  #
475
493
  # Options are passed to the underlying cache implementation.
476
494
  #
477
- # All implementations may not support this method.
495
+ # Some implementations may not support this method.
478
496
  def delete_matched(matcher, options = nil)
479
497
  raise NotImplementedError.new("#{self.class.name} does not support delete_matched")
480
498
  end
@@ -483,7 +501,7 @@ module ActiveSupport
483
501
  #
484
502
  # Options are passed to the underlying cache implementation.
485
503
  #
486
- # All implementations may not support this method.
504
+ # Some implementations may not support this method.
487
505
  def increment(name, amount = 1, options = nil)
488
506
  raise NotImplementedError.new("#{self.class.name} does not support increment")
489
507
  end
@@ -492,7 +510,7 @@ module ActiveSupport
492
510
  #
493
511
  # Options are passed to the underlying cache implementation.
494
512
  #
495
- # All implementations may not support this method.
513
+ # Some implementations may not support this method.
496
514
  def decrement(name, amount = 1, options = nil)
497
515
  raise NotImplementedError.new("#{self.class.name} does not support decrement")
498
516
  end
@@ -501,7 +519,7 @@ module ActiveSupport
501
519
  #
502
520
  # Options are passed to the underlying cache implementation.
503
521
  #
504
- # All implementations may not support this method.
522
+ # Some implementations may not support this method.
505
523
  def cleanup(options = nil)
506
524
  raise NotImplementedError.new("#{self.class.name} does not support cleanup")
507
525
  end
@@ -511,7 +529,7 @@ module ActiveSupport
511
529
  #
512
530
  # The options hash is passed to the underlying cache implementation.
513
531
  #
514
- # All implementations may not support this method.
532
+ # Some implementations may not support this method.
515
533
  def clear(options = nil)
516
534
  raise NotImplementedError.new("#{self.class.name} does not support clear")
517
535
  end
@@ -587,9 +605,13 @@ module ActiveSupport
587
605
  # Merges the default options with ones specific to a method call.
588
606
  def merged_options(call_options)
589
607
  if call_options
590
- options.merge(call_options)
608
+ if options.empty?
609
+ call_options
610
+ else
611
+ options.merge(call_options)
612
+ end
591
613
  else
592
- options.dup
614
+ options
593
615
  end
594
616
  end
595
617
 
@@ -634,7 +656,7 @@ module ActiveSupport
634
656
  if key.size > 1
635
657
  key = key.collect { |element| expanded_key(element) }
636
658
  else
637
- key = key.first
659
+ key = expanded_key(key.first)
638
660
  end
639
661
  when Hash
640
662
  key = key.sort_by { |k, _| k.to_s }.collect { |k, v| "#{k}=#{v}" }
@@ -685,7 +707,7 @@ module ActiveSupport
685
707
  end
686
708
 
687
709
  def get_entry_value(entry, name, options)
688
- instrument(:fetch_hit, name, options) {}
710
+ instrument(:fetch_hit, name, options) { }
689
711
  entry.value
690
712
  end
691
713
 
@@ -694,7 +716,7 @@ module ActiveSupport
694
716
  yield(name)
695
717
  end
696
718
 
697
- write(name, result, options)
719
+ write(name, result, options) unless result.nil? && options[:skip_nil]
698
720
  result
699
721
  end
700
722
  end
@@ -23,6 +23,9 @@ module ActiveSupport
23
23
  # +ClassMethods.set_callback+), and run the installed callbacks at the
24
24
  # appropriate times (via +run_callbacks+).
25
25
  #
26
+ # By default callbacks are halted by throwing +:abort+.
27
+ # See +ClassMethods.define_callbacks+ for details.
28
+ #
26
29
  # Three kinds of callbacks are supported: before callbacks, run before a
27
30
  # certain event; after callbacks, run after the event; and around callbacks,
28
31
  # blocks that surround the event, triggering it when they yield. Callback code
@@ -497,9 +500,7 @@ module ActiveSupport
497
500
  arg.halted || !@user_conditions.all? { |c| c.call(arg.target, arg.value) }
498
501
  end
499
502
 
500
- def nested
501
- @nested
502
- end
503
+ attr_reader :nested
503
504
 
504
505
  def final?
505
506
  !@call_template
@@ -578,7 +579,7 @@ module ActiveSupport
578
579
  end
579
580
 
580
581
  protected
581
- def chain; @chain; end
582
+ attr_reader :chain
582
583
 
583
584
  private
584
585
 
@@ -659,9 +660,17 @@ module ActiveSupport
659
660
  # * <tt>:if</tt> - A symbol or an array of symbols, each naming an instance
660
661
  # method or a proc; the callback will be called only when they all return
661
662
  # a true value.
663
+ #
664
+ # If a proc is given, its body is evaluated in the context of the
665
+ # current object. It can also optionally accept the current object as
666
+ # an argument.
662
667
  # * <tt>:unless</tt> - A symbol or an array of symbols, each naming an
663
668
  # instance method or a proc; the callback will be called only when they
664
669
  # all return a false value.
670
+ #
671
+ # If a proc is given, its body is evaluated in the context of the
672
+ # current object. It can also optionally accept the current object as
673
+ # an argument.
665
674
  # * <tt>:prepend</tt> - If +true+, the callback will be prepended to the
666
675
  # existing chain rather than appended.
667
676
  def set_callback(name, *filter_list, &block)
@@ -809,7 +818,9 @@ module ActiveSupport
809
818
  names.each do |name|
810
819
  name = name.to_sym
811
820
 
812
- set_callbacks name, CallbackChain.new(name, options)
821
+ ([self] + ActiveSupport::DescendantsTracker.descendants(self)).each do |target|
822
+ target.set_callbacks name, CallbackChain.new(name, options)
823
+ end
813
824
 
814
825
  module_eval <<-RUBY, __FILE__, __LINE__ + 1
815
826
  def _run_#{name}_callbacks(&block)