activesupport 6.0.0.beta2 → 6.0.2.rc1

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 (49) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +287 -3
  3. data/README.rdoc +2 -1
  4. data/lib/active_support.rb +1 -0
  5. data/lib/active_support/actionable_error.rb +48 -0
  6. data/lib/active_support/backtrace_cleaner.rb +5 -1
  7. data/lib/active_support/cache.rb +5 -5
  8. data/lib/active_support/cache/memory_store.rb +2 -0
  9. data/lib/active_support/cache/redis_cache_store.rb +9 -6
  10. data/lib/active_support/concern.rb +24 -1
  11. data/lib/active_support/configurable.rb +3 -3
  12. data/lib/active_support/core_ext/array/access.rb +18 -6
  13. data/lib/active_support/core_ext/class/attribute.rb +10 -15
  14. data/lib/active_support/core_ext/date_and_time/calculations.rb +0 -30
  15. data/lib/active_support/core_ext/digest.rb +3 -0
  16. data/lib/active_support/core_ext/enumerable.rb +24 -4
  17. data/lib/active_support/core_ext/hash/deep_transform_values.rb +2 -2
  18. data/lib/active_support/core_ext/hash/except.rb +1 -1
  19. data/lib/active_support/core_ext/module/attribute_accessors.rb +5 -5
  20. data/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb +5 -5
  21. data/lib/active_support/core_ext/module/delegation.rb +6 -0
  22. data/lib/active_support/core_ext/object/duplicable.rb +7 -117
  23. data/lib/active_support/core_ext/range/compare_range.rb +27 -12
  24. data/lib/active_support/core_ext/range/include_time_with_zone.rb +2 -2
  25. data/lib/active_support/core_ext/string/filters.rb +1 -1
  26. data/lib/active_support/core_ext/string/inflections.rb +7 -2
  27. data/lib/active_support/core_ext/string/output_safety.rb +51 -4
  28. data/lib/active_support/core_ext/time/calculations.rb +31 -2
  29. data/lib/active_support/dependencies.rb +41 -5
  30. data/lib/active_support/dependencies/zeitwerk_integration.rb +44 -21
  31. data/lib/active_support/deprecation/method_wrappers.rb +7 -18
  32. data/lib/active_support/deprecation/proxy_wrappers.rb +24 -3
  33. data/lib/active_support/descendants_tracker.rb +52 -6
  34. data/lib/active_support/duration.rb +2 -3
  35. data/lib/active_support/evented_file_update_checker.rb +14 -2
  36. data/lib/active_support/gem_version.rb +2 -2
  37. data/lib/active_support/hash_with_indifferent_access.rb +6 -3
  38. data/lib/active_support/i18n_railtie.rb +6 -1
  39. data/lib/active_support/inflector/transliterate.rb +43 -14
  40. data/lib/active_support/logger_thread_safe_level.rb +2 -1
  41. data/lib/active_support/notifications/fanout.rb +2 -2
  42. data/lib/active_support/notifications/instrumenter.rb +12 -11
  43. data/lib/active_support/ordered_hash.rb +1 -1
  44. data/lib/active_support/ordered_options.rb +1 -1
  45. data/lib/active_support/parameter_filter.rb +6 -1
  46. data/lib/active_support/security_utils.rb +1 -1
  47. data/lib/active_support/subscriber.rb +55 -6
  48. data/lib/active_support/testing/parallelization.rb +21 -2
  49. metadata +13 -14
@@ -3,54 +3,69 @@
3
3
  module ActiveSupport
4
4
  module CompareWithRange
5
5
  # Extends the default Range#=== to support range comparisons.
6
- # (1..5) === (1..5) # => true
7
- # (1..5) === (2..3) # => true
8
- # (1..5) === (2..6) # => false
6
+ # (1..5) === (1..5) # => true
7
+ # (1..5) === (2..3) # => true
8
+ # (1..5) === (1...6) # => true
9
+ # (1..5) === (2..6) # => false
9
10
  #
10
11
  # The native Range#=== behavior is untouched.
11
12
  # ('a'..'f') === ('c') # => true
12
13
  # (5..9) === (11) # => false
14
+ #
15
+ # The given range must be fully bounded, with both start and end.
13
16
  def ===(value)
14
17
  if value.is_a?(::Range)
15
18
  # 1...10 includes 1..9 but it does not include 1..10.
19
+ # 1..10 includes 1...11 but it does not include 1...12.
16
20
  operator = exclude_end? && !value.exclude_end? ? :< : :<=
17
- super(value.first) && value.last.send(operator, last)
21
+ value_max = !exclude_end? && value.exclude_end? ? value.max : value.last
22
+ super(value.first) && (self.end.nil? || value_max.send(operator, last))
18
23
  else
19
24
  super
20
25
  end
21
26
  end
22
27
 
23
28
  # Extends the default Range#include? to support range comparisons.
24
- # (1..5).include?(1..5) # => true
25
- # (1..5).include?(2..3) # => true
26
- # (1..5).include?(2..6) # => false
29
+ # (1..5).include?(1..5) # => true
30
+ # (1..5).include?(2..3) # => true
31
+ # (1..5).include?(1...6) # => true
32
+ # (1..5).include?(2..6) # => false
27
33
  #
28
34
  # The native Range#include? behavior is untouched.
29
35
  # ('a'..'f').include?('c') # => true
30
36
  # (5..9).include?(11) # => false
37
+ #
38
+ # The given range must be fully bounded, with both start and end.
31
39
  def include?(value)
32
40
  if value.is_a?(::Range)
33
41
  # 1...10 includes 1..9 but it does not include 1..10.
42
+ # 1..10 includes 1...11 but it does not include 1...12.
34
43
  operator = exclude_end? && !value.exclude_end? ? :< : :<=
35
- super(value.first) && value.last.send(operator, last)
44
+ value_max = !exclude_end? && value.exclude_end? ? value.max : value.last
45
+ super(value.first) && (self.end.nil? || value_max.send(operator, last))
36
46
  else
37
47
  super
38
48
  end
39
49
  end
40
50
 
41
51
  # Extends the default Range#cover? to support range comparisons.
42
- # (1..5).cover?(1..5) # => true
43
- # (1..5).cover?(2..3) # => true
44
- # (1..5).cover?(2..6) # => false
52
+ # (1..5).cover?(1..5) # => true
53
+ # (1..5).cover?(2..3) # => true
54
+ # (1..5).cover?(1...6) # => true
55
+ # (1..5).cover?(2..6) # => false
45
56
  #
46
57
  # The native Range#cover? behavior is untouched.
47
58
  # ('a'..'f').cover?('c') # => true
48
59
  # (5..9).cover?(11) # => false
60
+ #
61
+ # The given range must be fully bounded, with both start and end.
49
62
  def cover?(value)
50
63
  if value.is_a?(::Range)
51
64
  # 1...10 covers 1..9 but it does not cover 1..10.
65
+ # 1..10 covers 1...11 but it does not cover 1...12.
52
66
  operator = exclude_end? && !value.exclude_end? ? :< : :<=
53
- super(value.first) && value.last.send(operator, last)
67
+ value_max = !exclude_end? && value.exclude_end? ? value.max : value.last
68
+ super(value.first) && (self.end.nil? || value_max.send(operator, last))
54
69
  else
55
70
  super
56
71
  end
@@ -9,9 +9,9 @@ module ActiveSupport
9
9
  # (1.hour.ago..1.hour.from_now).include?(Time.current) # => true
10
10
  #
11
11
  def include?(value)
12
- if first.is_a?(TimeWithZone)
12
+ if self.begin.is_a?(TimeWithZone)
13
13
  cover?(value)
14
- elsif last.is_a?(TimeWithZone)
14
+ elsif self.end.is_a?(TimeWithZone)
15
15
  cover?(value)
16
16
  else
17
17
  super
@@ -75,7 +75,7 @@ class String
75
75
  length_with_room_for_omission
76
76
  end
77
77
 
78
- "#{self[0, stop]}#{omission}"
78
+ +"#{self[0, stop]}#{omission}"
79
79
  end
80
80
 
81
81
  # Truncates +text+ to at most <tt>bytesize</tt> bytes in length without
@@ -162,6 +162,11 @@ class String
162
162
 
163
163
  # Replaces special characters in a string so that it may be used as part of a 'pretty' URL.
164
164
  #
165
+ # If the optional parameter +locale+ is specified,
166
+ # the word will be parameterized as a word of that language.
167
+ # By default, this parameter is set to <tt>nil</tt> and it will use
168
+ # the configured <tt>I18n.locale</tt>.
169
+ #
165
170
  # class Person
166
171
  # def to_param
167
172
  # "#{id}-#{name.parameterize}"
@@ -187,8 +192,8 @@ class String
187
192
  #
188
193
  # <%= link_to(@person.name, person_path) %>
189
194
  # # => <a href="/person/1-Donald-E-Knuth">Donald E. Knuth</a>
190
- def parameterize(separator: "-", preserve_case: false)
191
- ActiveSupport::Inflector.parameterize(self, separator: separator, preserve_case: preserve_case)
195
+ def parameterize(separator: "-", preserve_case: false, locale: nil)
196
+ ActiveSupport::Inflector.parameterize(self, separator: separator, preserve_case: preserve_case, locale: locale)
192
197
  end
193
198
 
194
199
  # Creates the name of a table like Rails does for models to table names. This method
@@ -135,10 +135,12 @@ module ActiveSupport #:nodoc:
135
135
  class SafeBuffer < String
136
136
  UNSAFE_STRING_METHODS = %w(
137
137
  capitalize chomp chop delete delete_prefix delete_suffix
138
- downcase gsub lstrip next reverse rstrip slice squeeze strip
139
- sub succ swapcase tr tr_s unicode_normalize upcase
138
+ downcase lstrip next reverse rstrip slice squeeze strip
139
+ succ swapcase tr tr_s unicode_normalize upcase
140
140
  )
141
141
 
142
+ UNSAFE_STRING_METHODS_WITH_BACKREF = %w(gsub sub)
143
+
142
144
  alias_method :original_concat, :concat
143
145
  private :original_concat
144
146
 
@@ -199,14 +201,24 @@ module ActiveSupport #:nodoc:
199
201
  super(html_escape_interpolated_argument(value))
200
202
  end
201
203
 
202
- def []=(index, value)
203
- super(index, html_escape_interpolated_argument(value))
204
+ def []=(*args)
205
+ if args.count == 3
206
+ super(args[0], args[1], html_escape_interpolated_argument(args[2]))
207
+ else
208
+ super(args[0], html_escape_interpolated_argument(args[1]))
209
+ end
204
210
  end
205
211
 
206
212
  def +(other)
207
213
  dup.concat(other)
208
214
  end
209
215
 
216
+ def *(*)
217
+ new_safe_buffer = super
218
+ new_safe_buffer.instance_variable_set(:@html_safe, @html_safe)
219
+ new_safe_buffer
220
+ end
221
+
210
222
  def %(args)
211
223
  case args
212
224
  when Hash
@@ -249,11 +261,46 @@ module ActiveSupport #:nodoc:
249
261
  end
250
262
  end
251
263
 
264
+ UNSAFE_STRING_METHODS_WITH_BACKREF.each do |unsafe_method|
265
+ if unsafe_method.respond_to?(unsafe_method)
266
+ class_eval <<-EOT, __FILE__, __LINE__ + 1
267
+ def #{unsafe_method}(*args, &block) # def gsub(*args, &block)
268
+ if block # if block
269
+ to_str.#{unsafe_method}(*args) { |*params| # to_str.gsub(*args) { |*params|
270
+ set_block_back_references(block, $~) # set_block_back_references(block, $~)
271
+ block.call(*params) # block.call(*params)
272
+ } # }
273
+ else # else
274
+ to_str.#{unsafe_method}(*args) # to_str.gsub(*args)
275
+ end # end
276
+ end # end
277
+
278
+ def #{unsafe_method}!(*args, &block) # def gsub!(*args, &block)
279
+ @html_safe = false # @html_safe = false
280
+ if block # if block
281
+ super(*args) { |*params| # super(*args) { |*params|
282
+ set_block_back_references(block, $~) # set_block_back_references(block, $~)
283
+ block.call(*params) # block.call(*params)
284
+ } # }
285
+ else # else
286
+ super # super
287
+ end # end
288
+ end # end
289
+ EOT
290
+ end
291
+ end
292
+
252
293
  private
253
294
 
254
295
  def html_escape_interpolated_argument(arg)
255
296
  (!html_safe? || arg.html_safe?) ? arg : CGI.escapeHTML(arg.to_s)
256
297
  end
298
+
299
+ def set_block_back_references(block, match_data)
300
+ block.binding.eval("proc { |m| $~ = m }").call(match_data)
301
+ rescue ArgumentError
302
+ # Can't create binding from C level Proc
303
+ end
257
304
  end
258
305
  end
259
306
 
@@ -170,8 +170,7 @@ class Time
170
170
  options[:hours] = options.fetch(:hours, 0) + 24 * partial_days
171
171
  end
172
172
 
173
- d = to_date.advance(options)
174
- d = d.gregorian if d.julian?
173
+ d = to_date.gregorian.advance(options)
175
174
  time_advanced_by_date = change(year: d.year, month: d.month, day: d.day)
176
175
  seconds_to_advance = \
177
176
  options.fetch(:seconds, 0) +
@@ -312,4 +311,34 @@ class Time
312
311
  end
313
312
  alias_method :eql_without_coercion, :eql?
314
313
  alias_method :eql?, :eql_with_coercion
314
+
315
+ # Returns a new time the specified number of days ago.
316
+ def prev_day(days = 1)
317
+ advance(days: -days)
318
+ end
319
+
320
+ # Returns a new time the specified number of days in the future.
321
+ def next_day(days = 1)
322
+ advance(days: days)
323
+ end
324
+
325
+ # Returns a new time the specified number of months ago.
326
+ def prev_month(months = 1)
327
+ advance(months: -months)
328
+ end
329
+
330
+ # Returns a new time the specified number of months in the future.
331
+ def next_month(months = 1)
332
+ advance(months: months)
333
+ end
334
+
335
+ # Returns a new time the specified number of years ago.
336
+ def prev_year(years = 1)
337
+ advance(years: -years)
338
+ end
339
+
340
+ # Returns a new time the specified number of years in the future.
341
+ def next_year(years = 1)
342
+ advance(years: years)
343
+ end
315
344
  end
@@ -20,6 +20,9 @@ module ActiveSupport #:nodoc:
20
20
  module Dependencies #:nodoc:
21
21
  extend self
22
22
 
23
+ UNBOUND_METHOD_MODULE_NAME = Module.instance_method(:name)
24
+ private_constant :UNBOUND_METHOD_MODULE_NAME
25
+
23
26
  mattr_accessor :interlock, default: Interlock.new
24
27
 
25
28
  # :doc:
@@ -70,6 +73,11 @@ module ActiveSupport #:nodoc:
70
73
  # only once. All directories in this set must also be present in +autoload_paths+.
71
74
  mattr_accessor :autoload_once_paths, default: []
72
75
 
76
+ # This is a private set that collects all eager load paths during bootstrap.
77
+ # Useful for Zeitwerk integration. Its public interface is the config.* path
78
+ # accessors of each engine.
79
+ mattr_accessor :_eager_load_paths, default: Set.new
80
+
73
81
  # An array of qualified constant names that have been loaded. Adding a name
74
82
  # to this array will cause it to be unloaded the next time Dependencies are
75
83
  # cleared.
@@ -196,6 +204,11 @@ module ActiveSupport #:nodoc:
196
204
  end
197
205
  end
198
206
 
207
+ def self.include_into(base)
208
+ base.include(self)
209
+ append_features(base)
210
+ end
211
+
199
212
  def const_missing(const_name)
200
213
  from_mod = anonymous? ? guess_for_anonymous(const_name) : self
201
214
  Dependencies.load_missing_constant(from_mod, const_name)
@@ -225,6 +238,21 @@ module ActiveSupport #:nodoc:
225
238
  base.class_eval do
226
239
  define_method(:load, Kernel.instance_method(:load))
227
240
  private :load
241
+
242
+ define_method(:require, Kernel.instance_method(:require))
243
+ private :require
244
+ end
245
+ end
246
+
247
+ def self.include_into(base)
248
+ base.include(self)
249
+
250
+ if base.instance_method(:load).owner == base
251
+ base.remove_method(:load)
252
+ end
253
+
254
+ if base.instance_method(:require).owner == base
255
+ base.remove_method(:require)
228
256
  end
229
257
  end
230
258
 
@@ -321,9 +349,9 @@ module ActiveSupport #:nodoc:
321
349
  end
322
350
 
323
351
  def hook!
324
- Object.class_eval { include Loadable }
325
- Module.class_eval { include ModuleConstMissing }
326
- Exception.class_eval { include Blamable }
352
+ Loadable.include_into(Object)
353
+ ModuleConstMissing.include_into(Module)
354
+ Exception.include(Blamable)
327
355
  end
328
356
 
329
357
  def unhook!
@@ -634,7 +662,7 @@ module ActiveSupport #:nodoc:
634
662
 
635
663
  # Determine if the given constant has been automatically loaded.
636
664
  def autoloaded?(desc)
637
- return false if desc.is_a?(Module) && desc.anonymous?
665
+ return false if desc.is_a?(Module) && real_mod_name(desc).nil?
638
666
  name = to_constant_name desc
639
667
  return false unless qualified_const_defined?(name)
640
668
  autoloaded_constants.include?(name)
@@ -690,7 +718,7 @@ module ActiveSupport #:nodoc:
690
718
  when String then desc.sub(/^::/, "")
691
719
  when Symbol then desc.to_s
692
720
  when Module
693
- desc.name ||
721
+ real_mod_name(desc) ||
694
722
  raise(ArgumentError, "Anonymous modules have no name to be referenced by")
695
723
  else raise TypeError, "Not a valid constant descriptor: #{desc.inspect}"
696
724
  end
@@ -764,6 +792,14 @@ module ActiveSupport #:nodoc:
764
792
  def log(message)
765
793
  logger.debug("autoloading: #{message}") if logger && verbose
766
794
  end
795
+
796
+ private
797
+
798
+ # Returns the original name of a class or module even if `name` has been
799
+ # overridden.
800
+ def real_mod_name(mod)
801
+ UNBOUND_METHOD_MODULE_NAME.bind(mod).call
802
+ end
767
803
  end
768
804
  end
769
805
 
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "set"
3
4
  require "active_support/core_ext/string/inflections"
4
5
 
5
6
  module ActiveSupport
@@ -9,6 +10,8 @@ module ActiveSupport
9
10
  def clear
10
11
  Dependencies.unload_interlock do
11
12
  Rails.autoloaders.main.reload
13
+ rescue Zeitwerk::ReloadingDisabledError
14
+ raise "reloading is disabled because config.cache_classes is true"
12
15
  end
13
16
  end
14
17
 
@@ -21,12 +24,12 @@ module ActiveSupport
21
24
  end
22
25
 
23
26
  def autoloaded_constants
24
- (Rails.autoloaders.main.loaded + Rails.autoloaders.once.loaded).to_a
27
+ Rails.autoloaders.main.unloadable_cpaths
25
28
  end
26
29
 
27
30
  def autoloaded?(object)
28
- cpath = object.is_a?(Module) ? object.name : object.to_s
29
- Rails.autoloaders.any? { |autoloader| autoloader.loaded?(cpath) }
31
+ cpath = object.is_a?(Module) ? real_mod_name(object) : object.to_s
32
+ Rails.autoloaders.main.unloadable_cpath?(cpath)
30
33
  end
31
34
 
32
35
  def verbose=(verbose)
@@ -39,55 +42,75 @@ module ActiveSupport
39
42
  end
40
43
  end
41
44
 
45
+ module RequireDependency
46
+ def require_dependency(filename)
47
+ filename = filename.to_path if filename.respond_to?(:to_path)
48
+ if abspath = ActiveSupport::Dependencies.search_for_file(filename)
49
+ require abspath
50
+ else
51
+ require filename
52
+ end
53
+ end
54
+ end
55
+
42
56
  module Inflector
57
+ # Concurrent::Map is not needed. This is a private class, and overrides
58
+ # must be defined while the application boots.
59
+ @overrides = {}
60
+
43
61
  def self.camelize(basename, _abspath)
44
- basename.camelize
62
+ @overrides[basename] || basename.camelize
63
+ end
64
+
65
+ def self.inflect(overrides)
66
+ @overrides.merge!(overrides)
45
67
  end
46
68
  end
47
69
 
48
70
  class << self
49
- def take_over
50
- setup_autoloaders
51
- freeze_autoload_paths
71
+ def take_over(enable_reloading:)
72
+ setup_autoloaders(enable_reloading)
73
+ freeze_paths
52
74
  decorate_dependencies
53
75
  end
54
76
 
55
77
  private
56
78
 
57
- def setup_autoloaders
58
- Rails.autoloaders.each do |autoloader|
59
- autoloader.inflector = Inflector
60
- end
61
-
79
+ def setup_autoloaders(enable_reloading)
62
80
  Dependencies.autoload_paths.each do |autoload_path|
63
81
  # Zeitwerk only accepts existing directories in `push_dir` to
64
82
  # prevent misconfigurations.
65
83
  next unless File.directory?(autoload_path)
66
84
 
67
- if autoload_once?(autoload_path)
68
- Rails.autoloaders.once.push_dir(autoload_path)
69
- else
70
- Rails.autoloaders.main.push_dir(autoload_path)
71
- end
85
+ autoloader = \
86
+ autoload_once?(autoload_path) ? Rails.autoloaders.once : Rails.autoloaders.main
87
+
88
+ autoloader.push_dir(autoload_path)
89
+ autoloader.do_not_eager_load(autoload_path) unless eager_load?(autoload_path)
72
90
  end
73
91
 
92
+ Rails.autoloaders.main.enable_reloading if enable_reloading
74
93
  Rails.autoloaders.each(&:setup)
75
94
  end
76
95
 
77
96
  def autoload_once?(autoload_path)
78
- Dependencies.autoload_once_paths.include?(autoload_path) ||
79
- Gem.path.any? { |gem_path| autoload_path.to_s.start_with?(gem_path) }
97
+ Dependencies.autoload_once_paths.include?(autoload_path)
98
+ end
99
+
100
+ def eager_load?(autoload_path)
101
+ Dependencies._eager_load_paths.member?(autoload_path)
80
102
  end
81
103
 
82
- def freeze_autoload_paths
104
+ def freeze_paths
83
105
  Dependencies.autoload_paths.freeze
84
106
  Dependencies.autoload_once_paths.freeze
107
+ Dependencies._eager_load_paths.freeze
85
108
  end
86
109
 
87
110
  def decorate_dependencies
88
111
  Dependencies.unhook!
89
112
  Dependencies.singleton_class.prepend(Decorations)
90
- Object.class_eval { alias_method :require_dependency, :require }
113
+ Object.prepend(RequireDependency)
91
114
  end
92
115
  end
93
116
  end