activesupport 5.2.1.1 → 6.0.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 (143) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +416 -351
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +3 -2
  5. data/lib/active_support.rb +2 -1
  6. data/lib/active_support/actionable_error.rb +48 -0
  7. data/lib/active_support/backtrace_cleaner.rb +28 -1
  8. data/lib/active_support/cache.rb +45 -23
  9. data/lib/active_support/cache/file_store.rb +22 -22
  10. data/lib/active_support/cache/mem_cache_store.rb +5 -0
  11. data/lib/active_support/cache/memory_store.rb +9 -2
  12. data/lib/active_support/cache/null_store.rb +5 -0
  13. data/lib/active_support/cache/redis_cache_store.rb +37 -10
  14. data/lib/active_support/callbacks.rb +16 -5
  15. data/lib/active_support/concern.rb +31 -4
  16. data/lib/active_support/configurable.rb +7 -11
  17. data/lib/active_support/core_ext/array.rb +1 -1
  18. data/lib/active_support/core_ext/array/access.rb +18 -6
  19. data/lib/active_support/core_ext/array/extract.rb +21 -0
  20. data/lib/active_support/core_ext/array/prepend_and_append.rb +2 -6
  21. data/lib/active_support/core_ext/class/attribute.rb +11 -16
  22. data/lib/active_support/core_ext/class/subclasses.rb +1 -1
  23. data/lib/active_support/core_ext/date/calculations.rb +6 -5
  24. data/lib/active_support/core_ext/date_and_time/calculations.rb +24 -47
  25. data/lib/active_support/core_ext/date_time/calculations.rb +1 -1
  26. data/lib/active_support/core_ext/digest.rb +3 -0
  27. data/lib/active_support/core_ext/enumerable.rb +97 -73
  28. data/lib/active_support/core_ext/file/atomic.rb +1 -1
  29. data/lib/active_support/core_ext/hash.rb +1 -2
  30. data/lib/active_support/core_ext/hash/compact.rb +2 -26
  31. data/lib/active_support/core_ext/hash/deep_transform_values.rb +46 -0
  32. data/lib/active_support/core_ext/hash/except.rb +1 -1
  33. data/lib/active_support/core_ext/hash/keys.rb +0 -29
  34. data/lib/active_support/core_ext/hash/slice.rb +3 -25
  35. data/lib/active_support/core_ext/hash/transform_values.rb +2 -29
  36. data/lib/active_support/core_ext/integer/multiple.rb +1 -1
  37. data/lib/active_support/core_ext/kernel.rb +0 -1
  38. data/lib/active_support/core_ext/load_error.rb +1 -1
  39. data/lib/active_support/core_ext/module.rb +0 -1
  40. data/lib/active_support/core_ext/module/attribute_accessors.rb +7 -10
  41. data/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb +13 -19
  42. data/lib/active_support/core_ext/module/delegation.rb +33 -7
  43. data/lib/active_support/core_ext/module/introspection.rb +37 -13
  44. data/lib/active_support/core_ext/module/reachable.rb +1 -6
  45. data/lib/active_support/core_ext/module/redefine_method.rb +8 -17
  46. data/lib/active_support/core_ext/numeric.rb +0 -1
  47. data/lib/active_support/core_ext/numeric/conversions.rb +124 -128
  48. data/lib/active_support/core_ext/numeric/inquiry.rb +2 -25
  49. data/lib/active_support/core_ext/object/blank.rb +1 -2
  50. data/lib/active_support/core_ext/object/duplicable.rb +7 -114
  51. data/lib/active_support/core_ext/object/json.rb +1 -0
  52. data/lib/active_support/core_ext/object/try.rb +15 -7
  53. data/lib/active_support/core_ext/object/with_options.rb +1 -1
  54. data/lib/active_support/core_ext/range.rb +1 -1
  55. data/lib/active_support/core_ext/range/compare_range.rb +76 -0
  56. data/lib/active_support/core_ext/range/conversions.rb +31 -29
  57. data/lib/active_support/core_ext/range/include_range.rb +6 -22
  58. data/lib/active_support/core_ext/range/include_time_with_zone.rb +2 -2
  59. data/lib/active_support/core_ext/regexp.rb +0 -4
  60. data/lib/active_support/core_ext/securerandom.rb +23 -3
  61. data/lib/active_support/core_ext/string/access.rb +8 -0
  62. data/lib/active_support/core_ext/string/filters.rb +42 -1
  63. data/lib/active_support/core_ext/string/inflections.rb +7 -2
  64. data/lib/active_support/core_ext/string/multibyte.rb +4 -3
  65. data/lib/active_support/core_ext/string/output_safety.rb +63 -5
  66. data/lib/active_support/core_ext/string/strip.rb +3 -1
  67. data/lib/active_support/core_ext/time/calculations.rb +31 -2
  68. data/lib/active_support/core_ext/uri.rb +2 -1
  69. data/lib/active_support/current_attributes.rb +8 -0
  70. data/lib/active_support/dependencies.rb +74 -17
  71. data/lib/active_support/dependencies/zeitwerk_integration.rb +118 -0
  72. data/lib/active_support/deprecation.rb +1 -1
  73. data/lib/active_support/deprecation/behaviors.rb +1 -1
  74. data/lib/active_support/deprecation/method_wrappers.rb +13 -12
  75. data/lib/active_support/deprecation/proxy_wrappers.rb +24 -5
  76. data/lib/active_support/descendants_tracker.rb +56 -9
  77. data/lib/active_support/duration.rb +6 -5
  78. data/lib/active_support/duration/iso8601_parser.rb +2 -3
  79. data/lib/active_support/duration/iso8601_serializer.rb +3 -4
  80. data/lib/active_support/encrypted_configuration.rb +1 -5
  81. data/lib/active_support/encrypted_file.rb +2 -1
  82. data/lib/active_support/evented_file_update_checker.rb +39 -9
  83. data/lib/active_support/execution_wrapper.rb +1 -0
  84. data/lib/active_support/gem_version.rb +3 -3
  85. data/lib/active_support/hash_with_indifferent_access.rb +36 -18
  86. data/lib/active_support/i18n.rb +1 -0
  87. data/lib/active_support/i18n_railtie.rb +14 -2
  88. data/lib/active_support/inflector/inflections.rb +1 -4
  89. data/lib/active_support/inflector/methods.rb +17 -27
  90. data/lib/active_support/inflector/transliterate.rb +47 -18
  91. data/lib/active_support/json/decoding.rb +23 -23
  92. data/lib/active_support/json/encoding.rb +6 -2
  93. data/lib/active_support/key_generator.rb +0 -32
  94. data/lib/active_support/lazy_load_hooks.rb +5 -1
  95. data/lib/active_support/locale/en.rb +31 -0
  96. data/lib/active_support/log_subscriber.rb +31 -8
  97. data/lib/active_support/logger.rb +0 -15
  98. data/lib/active_support/logger_silence.rb +28 -12
  99. data/lib/active_support/logger_thread_safe_level.rb +28 -5
  100. data/lib/active_support/message_encryptor.rb +3 -5
  101. data/lib/active_support/message_verifier.rb +3 -3
  102. data/lib/active_support/multibyte/chars.rb +29 -48
  103. data/lib/active_support/multibyte/unicode.rb +44 -281
  104. data/lib/active_support/notifications.rb +41 -4
  105. data/lib/active_support/notifications/fanout.rb +100 -15
  106. data/lib/active_support/notifications/instrumenter.rb +80 -8
  107. data/lib/active_support/number_helper.rb +7 -0
  108. data/lib/active_support/number_helper/number_to_currency_converter.rb +2 -2
  109. data/lib/active_support/number_helper/number_to_delimited_converter.rb +3 -1
  110. data/lib/active_support/number_helper/number_to_human_converter.rb +3 -1
  111. data/lib/active_support/number_helper/number_to_human_size_converter.rb +3 -1
  112. data/lib/active_support/number_helper/number_to_percentage_converter.rb +3 -1
  113. data/lib/active_support/number_helper/number_to_phone_converter.rb +2 -0
  114. data/lib/active_support/number_helper/number_to_rounded_converter.rb +5 -3
  115. data/lib/active_support/ordered_hash.rb +1 -1
  116. data/lib/active_support/ordered_options.rb +1 -1
  117. data/lib/active_support/parameter_filter.rb +129 -0
  118. data/lib/active_support/rails.rb +0 -6
  119. data/lib/active_support/reloader.rb +4 -5
  120. data/lib/active_support/security_utils.rb +1 -1
  121. data/lib/active_support/subscriber.rb +65 -22
  122. data/lib/active_support/tagged_logging.rb +13 -4
  123. data/lib/active_support/test_case.rb +91 -0
  124. data/lib/active_support/testing/assertions.rb +15 -1
  125. data/lib/active_support/testing/deprecation.rb +0 -1
  126. data/lib/active_support/testing/file_fixtures.rb +2 -0
  127. data/lib/active_support/testing/isolation.rb +2 -2
  128. data/lib/active_support/testing/method_call_assertions.rb +28 -1
  129. data/lib/active_support/testing/parallelization.rb +128 -0
  130. data/lib/active_support/testing/stream.rb +1 -1
  131. data/lib/active_support/testing/time_helpers.rb +7 -7
  132. data/lib/active_support/time_with_zone.rb +15 -5
  133. data/lib/active_support/values/time_zone.rb +12 -7
  134. data/lib/active_support/xml_mini.rb +2 -9
  135. data/lib/active_support/xml_mini/jdom.rb +2 -2
  136. data/lib/active_support/xml_mini/libxml.rb +2 -2
  137. data/lib/active_support/xml_mini/libxmlsax.rb +4 -4
  138. data/lib/active_support/xml_mini/nokogiri.rb +2 -2
  139. data/lib/active_support/xml_mini/nokogirisax.rb +3 -3
  140. data/lib/active_support/xml_mini/rexml.rb +2 -2
  141. metadata +33 -10
  142. data/lib/active_support/core_ext/kernel/agnostics.rb +0 -13
  143. data/lib/active_support/values/unicode_tables.dat +0 -0
@@ -4,8 +4,4 @@ class Regexp #:nodoc:
4
4
  def multiline?
5
5
  options & MULTILINE == MULTILINE
6
6
  end
7
-
8
- def match?(string, pos = 0)
9
- !!match(string, pos)
10
- end unless //.respond_to?(:match?)
11
7
  end
@@ -4,17 +4,18 @@ require "securerandom"
4
4
 
5
5
  module SecureRandom
6
6
  BASE58_ALPHABET = ("0".."9").to_a + ("A".."Z").to_a + ("a".."z").to_a - ["0", "O", "I", "l"]
7
+ BASE36_ALPHABET = ("0".."9").to_a + ("a".."z").to_a
8
+
7
9
  # SecureRandom.base58 generates a random base58 string.
8
10
  #
9
- # The argument _n_ specifies the length, of the random string to be generated.
11
+ # The argument _n_ specifies the length of the random string to be generated.
10
12
  #
11
13
  # If _n_ is not specified or is +nil+, 16 is assumed. It may be larger in the future.
12
14
  #
13
- # The result may contain alphanumeric characters except 0, O, I and l
15
+ # The result may contain alphanumeric characters except 0, O, I and l.
14
16
  #
15
17
  # p SecureRandom.base58 # => "4kUgL2pdQMSCQtjE"
16
18
  # p SecureRandom.base58(24) # => "77TMHrHJFvFDwodq8w7Ev2m7"
17
- #
18
19
  def self.base58(n = 16)
19
20
  SecureRandom.random_bytes(n).unpack("C*").map do |byte|
20
21
  idx = byte % 64
@@ -22,4 +23,23 @@ module SecureRandom
22
23
  BASE58_ALPHABET[idx]
23
24
  end.join
24
25
  end
26
+
27
+ # SecureRandom.base36 generates a random base36 string in lowercase.
28
+ #
29
+ # The argument _n_ specifies the length of the random string to be generated.
30
+ #
31
+ # If _n_ is not specified or is +nil+, 16 is assumed. It may be larger in the future.
32
+ # This method can be used over +base58+ if a deterministic case key is necessary.
33
+ #
34
+ # The result will contain alphanumeric characters in lowercase.
35
+ #
36
+ # p SecureRandom.base36 # => "4kugl2pdqmscqtje"
37
+ # p SecureRandom.base36(24) # => "77tmhrhjfvfdwodq8w7ev2m7"
38
+ def self.base36(n = 16)
39
+ SecureRandom.random_bytes(n).unpack("C*").map do |byte|
40
+ idx = byte % 64
41
+ idx = SecureRandom.random_number(36) if idx >= 36
42
+ BASE36_ALPHABET[idx]
43
+ end.join
44
+ end
25
45
  end
@@ -75,6 +75,10 @@ class String
75
75
  # str.first(0) # => ""
76
76
  # str.first(6) # => "hello"
77
77
  def first(limit = 1)
78
+ ActiveSupport::Deprecation.warn(
79
+ "Calling String#first with a negative integer limit " \
80
+ "will raise an ArgumentError in Rails 6.1."
81
+ ) if limit < 0
78
82
  if limit == 0
79
83
  ""
80
84
  elsif limit >= size
@@ -95,6 +99,10 @@ class String
95
99
  # str.last(0) # => ""
96
100
  # str.last(6) # => "hello"
97
101
  def last(limit = 1)
102
+ ActiveSupport::Deprecation.warn(
103
+ "Calling String#last with a negative integer limit " \
104
+ "will raise an ArgumentError in Rails 6.1."
105
+ ) if limit < 0
98
106
  if limit == 0
99
107
  ""
100
108
  elsif limit >= size
@@ -75,7 +75,48 @@ 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
+ end
80
+
81
+ # Truncates +text+ to at most <tt>bytesize</tt> bytes in length without
82
+ # breaking string encoding by splitting multibyte characters or breaking
83
+ # grapheme clusters ("perceptual characters") by truncating at combining
84
+ # characters.
85
+ #
86
+ # >> "🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪".size
87
+ # => 20
88
+ # >> "🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪".bytesize
89
+ # => 80
90
+ # >> "🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪".truncate_bytes(20)
91
+ # => "🔪🔪🔪🔪…"
92
+ #
93
+ # The truncated text ends with the <tt>:omission</tt> string, defaulting
94
+ # to "…", for a total length not exceeding <tt>bytesize</tt>.
95
+ def truncate_bytes(truncate_at, omission: "…")
96
+ omission ||= ""
97
+
98
+ case
99
+ when bytesize <= truncate_at
100
+ dup
101
+ when omission.bytesize > truncate_at
102
+ raise ArgumentError, "Omission #{omission.inspect} is #{omission.bytesize}, larger than the truncation length of #{truncate_at} bytes"
103
+ when omission.bytesize == truncate_at
104
+ omission.dup
105
+ else
106
+ self.class.new.tap do |cut|
107
+ cut_at = truncate_at - omission.bytesize
108
+
109
+ scan(/\X/) do |grapheme|
110
+ if cut.bytesize + grapheme.bytesize <= cut_at
111
+ cut << grapheme
112
+ else
113
+ break
114
+ end
115
+ end
116
+
117
+ cut << omission
118
+ end
119
+ end
79
120
  end
80
121
 
81
122
  # Truncates a given +text+ after a given number of words (<tt>words_count</tt>):
@@ -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
@@ -11,12 +11,13 @@ class String
11
11
  # encapsulates the original string. A Unicode safe version of all the String methods are defined on this proxy
12
12
  # class. If the proxy class doesn't respond to a certain method, it's forwarded to the encapsulated string.
13
13
  #
14
- # >> "lj".upcase
15
- # => "lj"
16
14
  # >> "lj".mb_chars.upcase.to_s
17
15
  # => "LJ"
18
16
  #
19
- # NOTE: An above example is useful for pre Ruby 2.4. Ruby 2.4 supports Unicode case mappings.
17
+ # NOTE: Ruby 2.4 and later support native Unicode case mappings:
18
+ #
19
+ # >> "lj".upcase
20
+ # => "LJ"
20
21
  #
21
22
  # == Method chaining
22
23
  #
@@ -134,10 +134,13 @@ end
134
134
  module ActiveSupport #:nodoc:
135
135
  class SafeBuffer < String
136
136
  UNSAFE_STRING_METHODS = %w(
137
- capitalize chomp chop delete downcase gsub lstrip next reverse rstrip
138
- slice squeeze strip sub succ swapcase tr tr_s upcase
137
+ capitalize chomp chop delete delete_prefix delete_suffix
138
+ downcase lstrip next reverse rstrip slice squeeze strip
139
+ succ swapcase tr tr_s unicode_normalize upcase
139
140
  )
140
141
 
142
+ UNSAFE_STRING_METHODS_WITH_BACKREF = %w(gsub sub)
143
+
141
144
  alias_method :original_concat, :concat
142
145
  private :original_concat
143
146
 
@@ -149,9 +152,7 @@ module ActiveSupport #:nodoc:
149
152
  end
150
153
 
151
154
  def [](*args)
152
- if args.size < 2
153
- super
154
- elsif html_safe?
155
+ if html_safe?
155
156
  new_safe_buffer = super
156
157
 
157
158
  if new_safe_buffer
@@ -188,14 +189,36 @@ module ActiveSupport #:nodoc:
188
189
  end
189
190
  alias << concat
190
191
 
192
+ def insert(index, value)
193
+ super(index, html_escape_interpolated_argument(value))
194
+ end
195
+
191
196
  def prepend(value)
192
197
  super(html_escape_interpolated_argument(value))
193
198
  end
194
199
 
200
+ def replace(value)
201
+ super(html_escape_interpolated_argument(value))
202
+ end
203
+
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
210
+ end
211
+
195
212
  def +(other)
196
213
  dup.concat(other)
197
214
  end
198
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
+
199
222
  def %(args)
200
223
  case args
201
224
  when Hash
@@ -238,11 +261,46 @@ module ActiveSupport #:nodoc:
238
261
  end
239
262
  end
240
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
+
241
293
  private
242
294
 
243
295
  def html_escape_interpolated_argument(arg)
244
296
  (!html_safe? || arg.html_safe?) ? arg : CGI.escapeHTML(arg.to_s)
245
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
246
304
  end
247
305
  end
248
306
 
@@ -20,6 +20,8 @@ class String
20
20
  # Technically, it looks for the least indented non-empty line
21
21
  # in the whole string, and removes that amount of leading whitespace.
22
22
  def strip_heredoc
23
- gsub(/^#{scan(/^[ \t]*(?=\S)/).min}/, "".freeze)
23
+ gsub(/^#{scan(/^[ \t]*(?=\S)/).min}/, "").tap do |stripped|
24
+ stripped.freeze if frozen?
25
+ end
24
26
  end
25
27
  end
@@ -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
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "uri"
4
+
4
5
  if RUBY_VERSION < "2.6.0"
5
6
  require "active_support/core_ext/module/redefine_method"
6
7
  URI::Parser.class_eval do
@@ -10,7 +11,7 @@ if RUBY_VERSION < "2.6.0"
10
11
  # YK: My initial experiments say yes, but let's be sure please
11
12
  enc = str.encoding
12
13
  enc = Encoding::UTF_8 if enc == Encoding::US_ASCII
13
- str.gsub(escaped) { |match| [match[1, 2].hex].pack("C") }.force_encoding(enc)
14
+ str.dup.force_encoding(Encoding::ASCII_8BIT).gsub(escaped) { |match| [match[1, 2].hex].pack("C") }.force_encoding(enc)
14
15
  end
15
16
  end
16
17
  end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "active_support/callbacks"
4
+
3
5
  module ActiveSupport
4
6
  # Abstract super class that provides a thread-isolated attributes singleton, which resets automatically
5
7
  # before and after each request. This allows you to keep all the per-request attributes easily
@@ -117,10 +119,16 @@ module ActiveSupport
117
119
  end
118
120
  end
119
121
 
122
+ # Calls this block before #reset is called on the instance. Used for resetting external collaborators that depend on current values.
123
+ def before_reset(&block)
124
+ set_callback :reset, :before, &block
125
+ end
126
+
120
127
  # Calls this block after #reset is called on the instance. Used for resetting external collaborators, like Time.zone.
121
128
  def resets(&block)
122
129
  set_callback :reset, :after, &block
123
130
  end
131
+ alias_method :after_reset, :resets
124
132
 
125
133
  delegate :set, :reset, to: :instance
126
134
 
@@ -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.
@@ -79,6 +87,12 @@ module ActiveSupport #:nodoc:
79
87
  # to allow arbitrary constants to be marked for unloading.
80
88
  mattr_accessor :explicitly_unloadable_constants, default: []
81
89
 
90
+ # The logger used when tracing autoloads.
91
+ mattr_accessor :logger
92
+
93
+ # If true, trace autoloads with +logger.debug+.
94
+ mattr_accessor :verbose, default: false
95
+
82
96
  # The WatchStack keeps a stack of the modules being watched as files are
83
97
  # loaded. If a file in the process of being loaded (parent.rb) triggers the
84
98
  # load of another file (child.rb) the stack will ensure that child.rb
@@ -97,6 +111,8 @@ module ActiveSupport #:nodoc:
97
111
  # parent.rb then requires namespace/child.rb, the stack will look like
98
112
  # [[Object], [Namespace]].
99
113
 
114
+ attr_reader :watching
115
+
100
116
  def initialize
101
117
  @watching = []
102
118
  @stack = Hash.new { |h, k| h[k] = [] }
@@ -138,7 +154,7 @@ module ActiveSupport #:nodoc:
138
154
 
139
155
  # Normalize the list of new constants, and add them to the list we will return
140
156
  new_constants.each do |suffix|
141
- constants << ([namespace, suffix] - ["Object"]).join("::".freeze)
157
+ constants << ([namespace, suffix] - ["Object"]).join("::")
142
158
  end
143
159
  end
144
160
  constants
@@ -188,6 +204,11 @@ module ActiveSupport #:nodoc:
188
204
  end
189
205
  end
190
206
 
207
+ def self.include_into(base)
208
+ base.include(self)
209
+ append_features(base)
210
+ end
211
+
191
212
  def const_missing(const_name)
192
213
  from_mod = anonymous? ? guess_for_anonymous(const_name) : self
193
214
  Dependencies.load_missing_constant(from_mod, const_name)
@@ -217,6 +238,21 @@ module ActiveSupport #:nodoc:
217
238
  base.class_eval do
218
239
  define_method(:load, Kernel.instance_method(:load))
219
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)
220
256
  end
221
257
  end
222
258
 
@@ -248,7 +284,9 @@ module ActiveSupport #:nodoc:
248
284
 
249
285
  def load_dependency(file)
250
286
  if Dependencies.load? && Dependencies.constant_watch_stack.watching?
251
- Dependencies.new_constants_in(Object) { yield }
287
+ descs = Dependencies.constant_watch_stack.watching.flatten.uniq
288
+
289
+ Dependencies.new_constants_in(*descs) { yield }
252
290
  else
253
291
  yield
254
292
  end
@@ -311,9 +349,9 @@ module ActiveSupport #:nodoc:
311
349
  end
312
350
 
313
351
  def hook!
314
- Object.class_eval { include Loadable }
315
- Module.class_eval { include ModuleConstMissing }
316
- Exception.class_eval { include Blamable }
352
+ Loadable.include_into(Object)
353
+ ModuleConstMissing.include_into(Module)
354
+ Exception.include(Blamable)
317
355
  end
318
356
 
319
357
  def unhook!
@@ -345,7 +383,7 @@ module ActiveSupport #:nodoc:
345
383
  end
346
384
 
347
385
  def require_or_load(file_name, const_path = nil)
348
- file_name = $` if file_name =~ /\.rb\z/
386
+ file_name = file_name.chomp(".rb")
349
387
  expanded = File.expand_path(file_name)
350
388
  return if loaded.include?(expanded)
351
389
 
@@ -395,7 +433,7 @@ module ActiveSupport #:nodoc:
395
433
  # constant paths which would cause Dependencies to attempt to load this
396
434
  # file.
397
435
  def loadable_constants_for_path(path, bases = autoload_paths)
398
- path = $` if path =~ /\.rb\z/
436
+ path = path.chomp(".rb")
399
437
  expanded_path = File.expand_path(path)
400
438
  paths = []
401
439
 
@@ -404,7 +442,7 @@ module ActiveSupport #:nodoc:
404
442
  next unless expanded_path.start_with?(expanded_root)
405
443
 
406
444
  root_size = expanded_root.size
407
- next if expanded_path[root_size] != ?/.freeze
445
+ next if expanded_path[root_size] != ?/
408
446
 
409
447
  nesting = expanded_path[(root_size + 1)..-1]
410
448
  paths << nesting.camelize unless nesting.blank?
@@ -416,7 +454,7 @@ module ActiveSupport #:nodoc:
416
454
 
417
455
  # Search for a file in autoload_paths matching the provided suffix.
418
456
  def search_for_file(path_suffix)
419
- path_suffix = path_suffix.sub(/(\.rb)?$/, ".rb".freeze)
457
+ path_suffix += ".rb" unless path_suffix.ends_with?(".rb")
420
458
 
421
459
  autoload_paths.each do |root|
422
460
  path = File.join(root, path_suffix)
@@ -450,6 +488,7 @@ module ActiveSupport #:nodoc:
450
488
  return nil unless base_path = autoloadable_module?(path_suffix)
451
489
  mod = Module.new
452
490
  into.const_set const_name, mod
491
+ log("constant #{qualified_name} autoloaded (module autovivified from #{File.join(base_path, path_suffix)})")
453
492
  autoloaded_constants << qualified_name unless autoload_once_paths.include?(base_path)
454
493
  autoloaded_constants.uniq!
455
494
  mod
@@ -491,26 +530,31 @@ module ActiveSupport #:nodoc:
491
530
  raise ArgumentError, "A copy of #{from_mod} has been removed from the module tree but is still active!"
492
531
  end
493
532
 
494
- qualified_name = qualified_name_for from_mod, const_name
533
+ qualified_name = qualified_name_for(from_mod, const_name)
495
534
  path_suffix = qualified_name.underscore
496
535
 
497
536
  file_path = search_for_file(path_suffix)
498
537
 
499
538
  if file_path
500
539
  expanded = File.expand_path(file_path)
501
- expanded.sub!(/\.rb\z/, "".freeze)
540
+ expanded.sub!(/\.rb\z/, "")
502
541
 
503
542
  if loading.include?(expanded)
504
543
  raise "Circular dependency detected while autoloading constant #{qualified_name}"
505
544
  else
506
545
  require_or_load(expanded, qualified_name)
507
- raise LoadError, "Unable to autoload constant #{qualified_name}, expected #{file_path} to define it" unless from_mod.const_defined?(const_name, false)
508
- return from_mod.const_get(const_name)
546
+
547
+ if from_mod.const_defined?(const_name, false)
548
+ log("constant #{qualified_name} autoloaded from #{expanded}.rb")
549
+ return from_mod.const_get(const_name)
550
+ else
551
+ raise LoadError, "Unable to autoload constant #{qualified_name}, expected #{file_path} to define it"
552
+ end
509
553
  end
510
554
  elsif mod = autoload_module!(from_mod, const_name, qualified_name, path_suffix)
511
555
  return mod
512
- elsif (parent = from_mod.parent) && parent != from_mod &&
513
- ! from_mod.parents.any? { |p| p.const_defined?(const_name, false) }
556
+ elsif (parent = from_mod.module_parent) && parent != from_mod &&
557
+ ! from_mod.module_parents.any? { |p| p.const_defined?(const_name, false) }
514
558
  # If our parents do not have a constant named +const_name+ then we are free
515
559
  # to attempt to load upwards. If they do have such a constant, then this
516
560
  # const_missing must be due to from_mod::const_name, which should not
@@ -554,6 +598,7 @@ module ActiveSupport #:nodoc:
554
598
  # as the environment will be in an inconsistent state, e.g. other constants
555
599
  # may have already been unloaded and not accessible.
556
600
  def remove_unloadable_constants!
601
+ log("removing unloadable constants")
557
602
  autoloaded_constants.each { |const| remove_constant const }
558
603
  autoloaded_constants.clear
559
604
  Reference.clear!
@@ -617,7 +662,7 @@ module ActiveSupport #:nodoc:
617
662
 
618
663
  # Determine if the given constant has been automatically loaded.
619
664
  def autoloaded?(desc)
620
- return false if desc.is_a?(Module) && desc.anonymous?
665
+ return false if desc.is_a?(Module) && real_mod_name(desc).nil?
621
666
  name = to_constant_name desc
622
667
  return false unless qualified_const_defined?(name)
623
668
  autoloaded_constants.include?(name)
@@ -673,7 +718,7 @@ module ActiveSupport #:nodoc:
673
718
  when String then desc.sub(/^::/, "")
674
719
  when Symbol then desc.to_s
675
720
  when Module
676
- desc.name ||
721
+ real_mod_name(desc) ||
677
722
  raise(ArgumentError, "Anonymous modules have no name to be referenced by")
678
723
  else raise TypeError, "Not a valid constant descriptor: #{desc.inspect}"
679
724
  end
@@ -743,6 +788,18 @@ module ActiveSupport #:nodoc:
743
788
  # The constant is no longer reachable, just skip it.
744
789
  end
745
790
  end
791
+
792
+ def log(message)
793
+ logger.debug("autoloading: #{message}") if logger && verbose
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
746
803
  end
747
804
  end
748
805