activesupport 6.0.4.4 → 7.0.4.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 (212) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +257 -532
  3. data/MIT-LICENSE +1 -1
  4. data/lib/active_support/actionable_error.rb +1 -1
  5. data/lib/active_support/array_inquirer.rb +2 -2
  6. data/lib/active_support/backtrace_cleaner.rb +5 -5
  7. data/lib/active_support/benchmarkable.rb +3 -3
  8. data/lib/active_support/cache/file_store.rb +16 -10
  9. data/lib/active_support/cache/mem_cache_store.rb +163 -42
  10. data/lib/active_support/cache/memory_store.rb +57 -29
  11. data/lib/active_support/cache/null_store.rb +10 -2
  12. data/lib/active_support/cache/redis_cache_store.rb +79 -98
  13. data/lib/active_support/cache/strategy/local_cache.rb +49 -57
  14. data/lib/active_support/cache.rb +378 -179
  15. data/lib/active_support/callbacks.rb +230 -122
  16. data/lib/active_support/code_generator.rb +65 -0
  17. data/lib/active_support/concern.rb +49 -5
  18. data/lib/active_support/concurrency/load_interlock_aware_monitor.rb +2 -4
  19. data/lib/active_support/concurrency/share_lock.rb +2 -2
  20. data/lib/active_support/configurable.rb +9 -6
  21. data/lib/active_support/configuration_file.rb +51 -0
  22. data/lib/active_support/core_ext/array/access.rb +1 -5
  23. data/lib/active_support/core_ext/array/conversions.rb +13 -12
  24. data/lib/active_support/core_ext/array/deprecated_conversions.rb +25 -0
  25. data/lib/active_support/core_ext/array/grouping.rb +6 -6
  26. data/lib/active_support/core_ext/array/inquiry.rb +2 -2
  27. data/lib/active_support/core_ext/array.rb +1 -0
  28. data/lib/active_support/core_ext/benchmark.rb +2 -2
  29. data/lib/active_support/core_ext/big_decimal/conversions.rb +1 -1
  30. data/lib/active_support/core_ext/class/attribute.rb +34 -44
  31. data/lib/active_support/core_ext/class/subclasses.rb +9 -22
  32. data/lib/active_support/core_ext/date/blank.rb +1 -1
  33. data/lib/active_support/core_ext/date/calculations.rb +9 -9
  34. data/lib/active_support/core_ext/date/conversions.rb +16 -15
  35. data/lib/active_support/core_ext/date/deprecated_conversions.rb +26 -0
  36. data/lib/active_support/core_ext/date.rb +1 -0
  37. data/lib/active_support/core_ext/date_and_time/calculations.rb +17 -4
  38. data/lib/active_support/core_ext/date_and_time/compatibility.rb +15 -0
  39. data/lib/active_support/core_ext/date_time/blank.rb +1 -1
  40. data/lib/active_support/core_ext/date_time/conversions.rb +13 -13
  41. data/lib/active_support/core_ext/date_time/deprecated_conversions.rb +22 -0
  42. data/lib/active_support/core_ext/date_time.rb +1 -0
  43. data/lib/active_support/core_ext/digest/uuid.rb +39 -13
  44. data/lib/active_support/core_ext/enumerable.rb +164 -23
  45. data/lib/active_support/core_ext/file/atomic.rb +3 -1
  46. data/lib/active_support/core_ext/hash/conversions.rb +2 -3
  47. data/lib/active_support/core_ext/hash/deep_transform_values.rb +1 -1
  48. data/lib/active_support/core_ext/hash/indifferent_access.rb +3 -3
  49. data/lib/active_support/core_ext/hash/keys.rb +2 -2
  50. data/lib/active_support/core_ext/hash/slice.rb +3 -2
  51. data/lib/active_support/core_ext/kernel/reporting.rb +4 -4
  52. data/lib/active_support/core_ext/kernel/singleton_class.rb +1 -1
  53. data/lib/active_support/core_ext/load_error.rb +1 -1
  54. data/lib/active_support/core_ext/module/attr_internal.rb +2 -2
  55. data/lib/active_support/core_ext/module/attribute_accessors.rb +25 -29
  56. data/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb +26 -13
  57. data/lib/active_support/core_ext/module/concerning.rb +8 -2
  58. data/lib/active_support/core_ext/module/delegation.rb +40 -36
  59. data/lib/active_support/core_ext/module/introspection.rb +1 -25
  60. data/lib/active_support/core_ext/name_error.rb +23 -2
  61. data/lib/active_support/core_ext/numeric/conversions.rb +80 -73
  62. data/lib/active_support/core_ext/numeric/deprecated_conversions.rb +60 -0
  63. data/lib/active_support/core_ext/numeric.rb +1 -0
  64. data/lib/active_support/core_ext/object/acts_like.rb +29 -5
  65. data/lib/active_support/core_ext/object/blank.rb +2 -2
  66. data/lib/active_support/core_ext/object/deep_dup.rb +1 -1
  67. data/lib/active_support/core_ext/object/duplicable.rb +11 -0
  68. data/lib/active_support/core_ext/object/json.rb +42 -26
  69. data/lib/active_support/core_ext/object/to_query.rb +2 -2
  70. data/lib/active_support/core_ext/object/try.rb +20 -20
  71. data/lib/active_support/core_ext/object/with_options.rb +20 -1
  72. data/lib/active_support/core_ext/pathname/existence.rb +21 -0
  73. data/lib/active_support/core_ext/pathname.rb +3 -0
  74. data/lib/active_support/core_ext/range/compare_range.rb +6 -25
  75. data/lib/active_support/core_ext/range/conversions.rb +8 -8
  76. data/lib/active_support/core_ext/range/deprecated_conversions.rb +26 -0
  77. data/lib/active_support/core_ext/range/each.rb +1 -1
  78. data/lib/active_support/core_ext/range/include_time_with_zone.rb +4 -20
  79. data/lib/active_support/core_ext/range/overlaps.rb +1 -1
  80. data/lib/active_support/core_ext/range.rb +1 -1
  81. data/lib/active_support/core_ext/regexp.rb +8 -1
  82. data/lib/active_support/core_ext/securerandom.rb +1 -1
  83. data/lib/active_support/core_ext/string/access.rb +5 -24
  84. data/lib/active_support/core_ext/string/conversions.rb +3 -2
  85. data/lib/active_support/core_ext/string/filters.rb +1 -1
  86. data/lib/active_support/core_ext/string/inflections.rb +39 -5
  87. data/lib/active_support/core_ext/string/inquiry.rb +2 -1
  88. data/lib/active_support/core_ext/string/multibyte.rb +2 -2
  89. data/lib/active_support/core_ext/string/output_safety.rb +92 -41
  90. data/lib/active_support/core_ext/string/starts_ends_with.rb +2 -2
  91. data/lib/active_support/core_ext/symbol/starts_ends_with.rb +6 -0
  92. data/lib/active_support/core_ext/symbol.rb +3 -0
  93. data/lib/active_support/core_ext/time/calculations.rb +25 -7
  94. data/lib/active_support/core_ext/time/conversions.rb +15 -12
  95. data/lib/active_support/core_ext/time/deprecated_conversions.rb +22 -0
  96. data/lib/active_support/core_ext/time/zones.rb +7 -22
  97. data/lib/active_support/core_ext/time.rb +1 -0
  98. data/lib/active_support/core_ext/uri.rb +3 -23
  99. data/lib/active_support/core_ext.rb +2 -1
  100. data/lib/active_support/current_attributes/test_helper.rb +13 -0
  101. data/lib/active_support/current_attributes.rb +39 -16
  102. data/lib/active_support/dependencies/interlock.rb +10 -18
  103. data/lib/active_support/dependencies/require_dependency.rb +28 -0
  104. data/lib/active_support/dependencies.rb +58 -769
  105. data/lib/active_support/deprecation/behaviors.rb +23 -7
  106. data/lib/active_support/deprecation/disallowed.rb +56 -0
  107. data/lib/active_support/deprecation/instance_delegator.rb +0 -1
  108. data/lib/active_support/deprecation/method_wrappers.rb +6 -5
  109. data/lib/active_support/deprecation/proxy_wrappers.rb +4 -4
  110. data/lib/active_support/deprecation/reporting.rb +50 -7
  111. data/lib/active_support/deprecation.rb +7 -2
  112. data/lib/active_support/descendants_tracker.rb +174 -64
  113. data/lib/active_support/digest.rb +5 -3
  114. data/lib/active_support/duration/iso8601_parser.rb +3 -3
  115. data/lib/active_support/duration/iso8601_serializer.rb +24 -10
  116. data/lib/active_support/duration.rb +134 -55
  117. data/lib/active_support/encrypted_configuration.rb +13 -2
  118. data/lib/active_support/encrypted_file.rb +32 -3
  119. data/lib/active_support/environment_inquirer.rb +20 -0
  120. data/lib/active_support/error_reporter.rb +117 -0
  121. data/lib/active_support/evented_file_update_checker.rb +72 -138
  122. data/lib/active_support/execution_context/test_helper.rb +13 -0
  123. data/lib/active_support/execution_context.rb +53 -0
  124. data/lib/active_support/execution_wrapper.rb +43 -21
  125. data/lib/active_support/executor/test_helper.rb +7 -0
  126. data/lib/active_support/fork_tracker.rb +71 -0
  127. data/lib/active_support/gem_version.rb +3 -3
  128. data/lib/active_support/hash_with_indifferent_access.rb +51 -25
  129. data/lib/active_support/html_safe_translation.rb +43 -0
  130. data/lib/active_support/i18n.rb +1 -0
  131. data/lib/active_support/i18n_railtie.rb +14 -19
  132. data/lib/active_support/inflector/inflections.rb +24 -9
  133. data/lib/active_support/inflector/methods.rb +29 -49
  134. data/lib/active_support/inflector/transliterate.rb +5 -5
  135. data/lib/active_support/isolated_execution_state.rb +72 -0
  136. data/lib/active_support/json/decoding.rb +4 -4
  137. data/lib/active_support/json/encoding.rb +8 -4
  138. data/lib/active_support/key_generator.rb +23 -6
  139. data/lib/active_support/lazy_load_hooks.rb +28 -4
  140. data/lib/active_support/locale/en.yml +8 -4
  141. data/lib/active_support/log_subscriber/test_helper.rb +2 -2
  142. data/lib/active_support/log_subscriber.rb +23 -5
  143. data/lib/active_support/logger.rb +1 -1
  144. data/lib/active_support/logger_silence.rb +2 -26
  145. data/lib/active_support/logger_thread_safe_level.rb +34 -21
  146. data/lib/active_support/message_encryptor.rb +16 -13
  147. data/lib/active_support/message_verifier.rb +50 -18
  148. data/lib/active_support/messages/metadata.rb +2 -2
  149. data/lib/active_support/messages/rotation_configuration.rb +2 -1
  150. data/lib/active_support/messages/rotator.rb +6 -5
  151. data/lib/active_support/multibyte/chars.rb +13 -52
  152. data/lib/active_support/multibyte/unicode.rb +1 -87
  153. data/lib/active_support/multibyte.rb +1 -1
  154. data/lib/active_support/notifications/fanout.rb +110 -69
  155. data/lib/active_support/notifications/instrumenter.rb +37 -29
  156. data/lib/active_support/notifications.rb +55 -28
  157. data/lib/active_support/number_helper/number_converter.rb +2 -4
  158. data/lib/active_support/number_helper/number_to_currency_converter.rb +11 -6
  159. data/lib/active_support/number_helper/number_to_delimited_converter.rb +1 -1
  160. data/lib/active_support/number_helper/number_to_human_converter.rb +1 -1
  161. data/lib/active_support/number_helper/number_to_human_size_converter.rb +2 -2
  162. data/lib/active_support/number_helper/number_to_phone_converter.rb +1 -1
  163. data/lib/active_support/number_helper/number_to_rounded_converter.rb +9 -5
  164. data/lib/active_support/number_helper/rounding_helper.rb +12 -32
  165. data/lib/active_support/number_helper.rb +29 -16
  166. data/lib/active_support/option_merger.rb +11 -18
  167. data/lib/active_support/ordered_hash.rb +1 -1
  168. data/lib/active_support/ordered_options.rb +9 -3
  169. data/lib/active_support/parameter_filter.rb +21 -11
  170. data/lib/active_support/per_thread_registry.rb +6 -1
  171. data/lib/active_support/rails.rb +1 -4
  172. data/lib/active_support/railtie.rb +77 -5
  173. data/lib/active_support/reloader.rb +1 -1
  174. data/lib/active_support/rescuable.rb +16 -16
  175. data/lib/active_support/ruby_features.rb +7 -0
  176. data/lib/active_support/secure_compare_rotator.rb +51 -0
  177. data/lib/active_support/security_utils.rb +19 -12
  178. data/lib/active_support/string_inquirer.rb +2 -2
  179. data/lib/active_support/subscriber.rb +19 -25
  180. data/lib/active_support/tagged_logging.rb +31 -6
  181. data/lib/active_support/test_case.rb +13 -21
  182. data/lib/active_support/testing/assertions.rb +50 -13
  183. data/lib/active_support/testing/deprecation.rb +52 -1
  184. data/lib/active_support/testing/isolation.rb +2 -2
  185. data/lib/active_support/testing/method_call_assertions.rb +5 -5
  186. data/lib/active_support/testing/parallelization/server.rb +82 -0
  187. data/lib/active_support/testing/parallelization/worker.rb +103 -0
  188. data/lib/active_support/testing/parallelization.rb +16 -95
  189. data/lib/active_support/testing/parallelize_executor.rb +76 -0
  190. data/lib/active_support/testing/stream.rb +3 -5
  191. data/lib/active_support/testing/tagged_logging.rb +1 -1
  192. data/lib/active_support/testing/time_helpers.rb +53 -5
  193. data/lib/active_support/time_with_zone.rb +126 -62
  194. data/lib/active_support/values/time_zone.rb +54 -23
  195. data/lib/active_support/version.rb +1 -1
  196. data/lib/active_support/xml_mini/jdom.rb +1 -1
  197. data/lib/active_support/xml_mini/libxml.rb +5 -5
  198. data/lib/active_support/xml_mini/libxmlsax.rb +1 -1
  199. data/lib/active_support/xml_mini/nokogiri.rb +4 -4
  200. data/lib/active_support/xml_mini/nokogirisax.rb +1 -1
  201. data/lib/active_support/xml_mini/rexml.rb +9 -2
  202. data/lib/active_support/xml_mini.rb +5 -4
  203. data/lib/active_support.rb +29 -1
  204. metadata +46 -45
  205. data/lib/active_support/core_ext/array/prepend_and_append.rb +0 -5
  206. data/lib/active_support/core_ext/hash/compact.rb +0 -5
  207. data/lib/active_support/core_ext/hash/transform_values.rb +0 -5
  208. data/lib/active_support/core_ext/marshal.rb +0 -24
  209. data/lib/active_support/core_ext/module/reachable.rb +0 -6
  210. data/lib/active_support/core_ext/numeric/inquiry.rb +0 -5
  211. data/lib/active_support/core_ext/range/include_range.rb +0 -9
  212. data/lib/active_support/dependencies/zeitwerk_integration.rb +0 -117
@@ -3,6 +3,7 @@
3
3
  require "active_support/core_ext/hash/keys"
4
4
  require "active_support/core_ext/hash/reverse_merge"
5
5
  require "active_support/core_ext/hash/except"
6
+ require "active_support/core_ext/hash/slice"
6
7
 
7
8
  module ActiveSupport
8
9
  # Implements a hash where keys <tt>:foo</tt> and <tt>"foo"</tt> are considered
@@ -64,14 +65,16 @@ module ActiveSupport
64
65
  self
65
66
  end
66
67
 
67
- def initialize(constructor = {})
68
+ def initialize(constructor = nil)
68
69
  if constructor.respond_to?(:to_hash)
69
70
  super()
70
71
  update(constructor)
71
72
 
72
- hash = constructor.to_hash
73
+ hash = constructor.is_a?(Hash) ? constructor : constructor.to_hash
73
74
  self.default = hash.default if hash.default
74
75
  self.default_proc = hash.default_proc if hash.default_proc
76
+ elsif constructor.nil?
77
+ super()
75
78
  else
76
79
  super(constructor)
77
80
  end
@@ -91,12 +94,12 @@ module ActiveSupport
91
94
  #
92
95
  # This value can be later fetched using either +:key+ or <tt>'key'</tt>.
93
96
  def []=(key, value)
94
- regular_writer(convert_key(key), convert_value(value, for: :assignment))
97
+ regular_writer(convert_key(key), convert_value(value, conversion: :assignment))
95
98
  end
96
99
 
97
100
  alias_method :store, :[]=
98
101
 
99
- # Updates the receiver in-place, merging in the hash passed as argument:
102
+ # Updates the receiver in-place, merging in the hashes passed as arguments:
100
103
  #
101
104
  # hash_1 = ActiveSupport::HashWithIndifferentAccess.new
102
105
  # hash_1[:key] = 'value'
@@ -106,11 +109,14 @@ module ActiveSupport
106
109
  #
107
110
  # hash_1.update(hash_2) # => {"key"=>"New Value!"}
108
111
  #
109
- # The argument can be either an
112
+ # hash = ActiveSupport::HashWithIndifferentAccess.new
113
+ # hash.update({ "a" => 1 }, { "b" => 2 }) # => { "a" => 1, "b" => 2 }
114
+ #
115
+ # The arguments can be either an
110
116
  # <tt>ActiveSupport::HashWithIndifferentAccess</tt> or a regular +Hash+.
111
117
  # In either case the merge respects the semantics of indifferent access.
112
118
  #
113
- # If the argument is a regular hash with keys +:key+ and +"key"+ only one
119
+ # If the argument is a regular hash with keys +:key+ and <tt>"key"</tt> only one
114
120
  # of the values end up in the receiver, but which one is unspecified.
115
121
  #
116
122
  # When given a block, the value for duplicated keys will be determined
@@ -121,18 +127,15 @@ module ActiveSupport
121
127
  # hash_1[:key] = 10
122
128
  # hash_2['key'] = 12
123
129
  # hash_1.update(hash_2) { |key, old, new| old + new } # => {"key"=>22}
124
- def update(other_hash)
125
- if other_hash.is_a? HashWithIndifferentAccess
126
- super(other_hash)
130
+ def update(*other_hashes, &block)
131
+ if other_hashes.size == 1
132
+ update_with_single_argument(other_hashes.first, block)
127
133
  else
128
- other_hash.to_hash.each_pair do |key, value|
129
- if block_given? && key?(key)
130
- value = yield(convert_key(key), self[key], value)
131
- end
132
- regular_writer(convert_key(key), convert_value(value))
134
+ other_hashes.each do |other_hash|
135
+ update_with_single_argument(other_hash, block)
133
136
  end
134
- self
135
137
  end
138
+ self
136
139
  end
137
140
 
138
141
  alias_method :merge!, :update
@@ -259,8 +262,8 @@ module ActiveSupport
259
262
  # This method has the same semantics of +update+, except it does not
260
263
  # modify the receiver but rather returns a new hash with indifferent
261
264
  # access with the result of the merge.
262
- def merge(hash, &block)
263
- dup.update(hash, &block)
265
+ def merge(*hashes, &block)
266
+ dup.update(*hashes, &block)
264
267
  end
265
268
 
266
269
  # Like +merge+ but the other way around: Merges the receiver into the
@@ -293,6 +296,10 @@ module ActiveSupport
293
296
  super(convert_key(key))
294
297
  end
295
298
 
299
+ # Returns a hash with indifferent access that includes everything except given keys.
300
+ # hash = { a: "x", b: "y", c: 10 }.with_indifferent_access
301
+ # hash.except(:a, "b") # => {c: 10}.with_indifferent_access
302
+ # hash # => { a: "x", b: "y", c: 10 }.with_indifferent_access
296
303
  def except(*keys)
297
304
  slice(*self.keys - keys.map { |key| convert_key(key) })
298
305
  end
@@ -357,40 +364,59 @@ module ActiveSupport
357
364
  set_defaults(_new_hash)
358
365
 
359
366
  each do |key, value|
360
- _new_hash[key] = convert_value(value, for: :to_hash)
367
+ _new_hash[key] = convert_value(value, conversion: :to_hash)
361
368
  end
362
369
  _new_hash
363
370
  end
364
371
 
365
372
  private
366
- def convert_key(key) # :doc:
367
- key.kind_of?(Symbol) ? key.to_s : key
373
+ if Symbol.method_defined?(:name)
374
+ def convert_key(key)
375
+ key.kind_of?(Symbol) ? key.name : key
376
+ end
377
+ else
378
+ def convert_key(key)
379
+ key.kind_of?(Symbol) ? key.to_s : key
380
+ end
368
381
  end
369
382
 
370
- def convert_value(value, options = {}) # :doc:
383
+ def convert_value(value, conversion: nil)
371
384
  if value.is_a? Hash
372
- if options[:for] == :to_hash
385
+ if conversion == :to_hash
373
386
  value.to_hash
374
387
  else
375
388
  value.nested_under_indifferent_access
376
389
  end
377
390
  elsif value.is_a?(Array)
378
- if options[:for] != :assignment || value.frozen?
391
+ if conversion != :assignment || value.frozen?
379
392
  value = value.dup
380
393
  end
381
- value.map! { |e| convert_value(e, options) }
394
+ value.map! { |e| convert_value(e, conversion: conversion) }
382
395
  else
383
396
  value
384
397
  end
385
398
  end
386
399
 
387
- def set_defaults(target) # :doc:
400
+ def set_defaults(target)
388
401
  if default_proc
389
402
  target.default_proc = default_proc.dup
390
403
  else
391
404
  target.default = default
392
405
  end
393
406
  end
407
+
408
+ def update_with_single_argument(other_hash, block)
409
+ if other_hash.is_a? HashWithIndifferentAccess
410
+ regular_update(other_hash, &block)
411
+ else
412
+ other_hash.to_hash.each_pair do |key, value|
413
+ if block && key?(key)
414
+ value = block.call(convert_key(key), self[key], value)
415
+ end
416
+ regular_writer(convert_key(key), convert_value(value))
417
+ end
418
+ end
419
+ end
394
420
  end
395
421
  end
396
422
 
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveSupport
4
+ module HtmlSafeTranslation # :nodoc:
5
+ extend self
6
+
7
+ def translate(key, **options)
8
+ if html_safe_translation_key?(key)
9
+ html_safe_options = html_escape_translation_options(options)
10
+ translation = I18n.translate(key, **html_safe_options)
11
+ html_safe_translation(translation)
12
+ else
13
+ I18n.translate(key, **options)
14
+ end
15
+ end
16
+
17
+ private
18
+ def html_safe_translation_key?(key)
19
+ /(?:_|\b)html\z/.match?(key)
20
+ end
21
+
22
+ def html_escape_translation_options(options)
23
+ options.each do |name, value|
24
+ unless i18n_option?(name) || (name == :count && value.is_a?(Numeric))
25
+ options[name] = ERB::Util.html_escape(value.to_s)
26
+ end
27
+ end
28
+ end
29
+
30
+ def i18n_option?(name)
31
+ (@i18n_option_names ||= I18n::RESERVED_KEYS.to_set).include?(name)
32
+ end
33
+
34
+
35
+ def html_safe_translation(translation)
36
+ if translation.respond_to?(:map)
37
+ translation.map { |element| element.respond_to?(:html_safe) ? element.html_safe : element }
38
+ else
39
+ translation.respond_to?(:html_safe) ? translation.html_safe : translation
40
+ end
41
+ end
42
+ end
43
+ end
@@ -5,6 +5,7 @@ require "active_support/core_ext/hash/except"
5
5
  require "active_support/core_ext/hash/slice"
6
6
  begin
7
7
  require "i18n"
8
+ require "i18n/backend/fallbacks"
8
9
  rescue LoadError => e
9
10
  $stderr.puts "The i18n gem is not available. Please add it to your Gemfile and run bundle install"
10
11
  raise e
@@ -12,9 +12,7 @@ module I18n
12
12
  config.i18n.load_path = []
13
13
  config.i18n.fallbacks = ActiveSupport::OrderedOptions.new
14
14
 
15
- if I18n.respond_to?(:eager_load!)
16
- config.eager_load_namespaces << I18n
17
- end
15
+ config.eager_load_namespaces << I18n
18
16
 
19
17
  # Set the i18n configuration after initialization since a lot of
20
18
  # configuration is still usually done in application initializers.
@@ -50,8 +48,10 @@ module I18n
50
48
  app.config.i18n.load_path.unshift(*value.flat_map(&:existent))
51
49
  when :load_path
52
50
  I18n.load_path += value
51
+ when :raise_on_missing_translations
52
+ forward_raise_on_missing_translations_config(app)
53
53
  else
54
- I18n.send("#{setting}=", value)
54
+ I18n.public_send("#{setting}=", value)
55
55
  end
56
56
  end
57
57
 
@@ -64,8 +64,6 @@ module I18n
64
64
  reloader = app.config.file_watcher.new(I18n.load_path.dup, directories) do
65
65
  I18n.load_path.keep_if { |p| File.exist?(p) }
66
66
  I18n.load_path |= reloadable_paths.flat_map(&:existent)
67
-
68
- I18n.reload!
69
67
  end
70
68
 
71
69
  app.reloaders << reloader
@@ -77,6 +75,16 @@ module I18n
77
75
  @i18n_inited = true
78
76
  end
79
77
 
78
+ def self.forward_raise_on_missing_translations_config(app)
79
+ ActiveSupport.on_load(:action_view) do
80
+ ActionView::Helpers::TranslationHelper.raise_on_missing_translations = app.config.i18n.raise_on_missing_translations
81
+ end
82
+
83
+ ActiveSupport.on_load(:action_controller) do
84
+ AbstractController::Translation.raise_on_missing_translations = app.config.i18n.raise_on_missing_translations
85
+ end
86
+ end
87
+
80
88
  def self.include_fallbacks_module
81
89
  I18n.backend.class.include(I18n::Backend::Fallbacks)
82
90
  end
@@ -94,19 +102,6 @@ module I18n
94
102
  [I18n.default_locale]
95
103
  end
96
104
 
97
- if args.empty? || args.first.is_a?(Hash)
98
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
99
- Using I18n fallbacks with an empty `defaults` sets the defaults to
100
- include the `default_locale`. This behavior will change in Rails 6.1.
101
- If you desire the default locale to be included in the defaults, please
102
- explicitly configure it with `config.i18n.fallbacks.defaults =
103
- [I18n.default_locale]` or `config.i18n.fallbacks = [I18n.default_locale,
104
- {...}]`. If you want to opt-in to the new behavior, use
105
- `config.i18n.fallbacks.defaults = [nil, {...}]`.
106
- MSG
107
- args.unshift I18n.default_locale
108
- end
109
-
110
105
  I18n.fallbacks = I18n::Locale::Fallbacks.new(*args)
111
106
  end
112
107
 
@@ -2,7 +2,6 @@
2
2
 
3
3
  require "concurrent/map"
4
4
  require "active_support/i18n"
5
- require "active_support/deprecation"
6
5
 
7
6
  module ActiveSupport
8
7
  module Inflector
@@ -17,13 +16,13 @@ module ActiveSupport
17
16
  # inflect.plural /^(ox)$/i, '\1\2en'
18
17
  # inflect.singular /^(ox)en/i, '\1'
19
18
  #
20
- # inflect.irregular 'octopus', 'octopi'
19
+ # inflect.irregular 'cactus', 'cacti'
21
20
  #
22
21
  # inflect.uncountable 'equipment'
23
22
  # end
24
23
  #
25
24
  # New rules are added at the top. So in the example above, the irregular
26
- # rule for octopus will now be the first of the pluralization and
25
+ # rule for cactus will now be the first of the pluralization and
27
26
  # singularization rules that is runs. This guarantees that your rules run
28
27
  # before any of the rules that may already have been loaded.
29
28
  class Inflections
@@ -65,6 +64,13 @@ module ActiveSupport
65
64
  @__instance__[locale] ||= new
66
65
  end
67
66
 
67
+ def self.instance_or_fallback(locale)
68
+ I18n.fallbacks[locale].each do |k|
69
+ return @__instance__[k] if @__instance__.key?(k)
70
+ end
71
+ instance(locale)
72
+ end
73
+
68
74
  attr_reader :plurals, :singulars, :uncountables, :humans, :acronyms
69
75
 
70
76
  attr_reader :acronyms_camelize_regex, :acronyms_underscore_regex # :nodoc:
@@ -77,7 +83,7 @@ module ActiveSupport
77
83
  # Private, for the test suite.
78
84
  def initialize_dup(orig) # :nodoc:
79
85
  %w(plurals singulars uncountables humans acronyms).each do |scope|
80
- instance_variable_set("@#{scope}", orig.send(scope).dup)
86
+ instance_variable_set("@#{scope}", orig.public_send(scope).dup)
81
87
  end
82
88
  define_acronym_regex_patterns
83
89
  end
@@ -161,7 +167,7 @@ module ActiveSupport
161
167
  # regular expressions. You simply pass the irregular in singular and
162
168
  # plural form.
163
169
  #
164
- # irregular 'octopus', 'octopi'
170
+ # irregular 'cactus', 'cacti'
165
171
  # irregular 'person', 'people'
166
172
  def irregular(singular, plural)
167
173
  @uncountables.delete(singular)
@@ -216,15 +222,24 @@ module ActiveSupport
216
222
  # Clears the loaded inflections within a given scope (default is
217
223
  # <tt>:all</tt>). Give the scope as a symbol of the inflection type, the
218
224
  # options are: <tt>:plurals</tt>, <tt>:singulars</tt>, <tt>:uncountables</tt>,
219
- # <tt>:humans</tt>.
225
+ # <tt>:humans</tt>, <tt>:acronyms</tt>.
220
226
  #
221
227
  # clear :all
222
228
  # clear :plurals
223
229
  def clear(scope = :all)
224
230
  case scope
225
231
  when :all
226
- @plurals, @singulars, @uncountables, @humans = [], [], Uncountables.new, []
227
- else
232
+ clear(:acronyms)
233
+ clear(:plurals)
234
+ clear(:singulars)
235
+ clear(:uncountables)
236
+ clear(:humans)
237
+ when :acronyms
238
+ @acronyms = {}
239
+ define_acronym_regex_patterns
240
+ when :uncountables
241
+ @uncountables = Uncountables.new
242
+ when :plurals, :singulars, :humans
228
243
  instance_variable_set "@#{scope}", []
229
244
  end
230
245
  end
@@ -249,7 +264,7 @@ module ActiveSupport
249
264
  if block_given?
250
265
  yield Inflections.instance(locale)
251
266
  else
252
- Inflections.instance(locale)
267
+ Inflections.instance_or_fallback(locale)
253
268
  end
254
269
  end
255
270
  end
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "active_support/inflections"
4
+ require "active_support/core_ext/object/blank"
4
5
 
5
6
  module ActiveSupport
6
7
  # The Inflector transforms words from singular to plural, class names to table
@@ -67,13 +68,17 @@ module ActiveSupport
67
68
  # camelize(underscore('SSLError')) # => "SslError"
68
69
  def camelize(term, uppercase_first_letter = true)
69
70
  string = term.to_s
70
- if uppercase_first_letter
71
- string = string.sub(/^[a-z\d]*/) { |match| inflections.acronyms[match] || match.capitalize }
71
+ # String#camelize takes a symbol (:upper or :lower), so here we also support :lower to keep the methods consistent.
72
+ if !uppercase_first_letter || uppercase_first_letter == :lower
73
+ string = string.sub(inflections.acronyms_camelize_regex) { |match| match.downcase! || match }
72
74
  else
73
- string = string.sub(inflections.acronyms_camelize_regex) { |match| match.downcase }
75
+ string = string.sub(/^[a-z\d]*/) { |match| inflections.acronyms[match] || match.capitalize! || match }
76
+ end
77
+ string.gsub!(/(?:_|(\/))([a-z\d]*)/i) do
78
+ word = $2
79
+ substituted = inflections.acronyms[word] || word.capitalize! || word
80
+ $1 ? "::#{substituted}" : substituted
74
81
  end
75
- string.gsub!(/(?:_|(\/))([a-z\d]*)/i) { "#{$1}#{inflections.acronyms[$2] || $2.capitalize}" }
76
- string.gsub!("/", "::")
77
82
  string
78
83
  end
79
84
 
@@ -89,11 +94,10 @@ module ActiveSupport
89
94
  #
90
95
  # camelize(underscore('SSLError')) # => "SslError"
91
96
  def underscore(camel_cased_word)
92
- return camel_cased_word unless /[A-Z-]|::/.match?(camel_cased_word)
97
+ return camel_cased_word.to_s unless /[A-Z-]|::/.match?(camel_cased_word)
93
98
  word = camel_cased_word.to_s.gsub("::", "/")
94
99
  word.gsub!(inflections.acronyms_underscore_regex) { "#{$1 && '_' }#{$2.downcase}" }
95
- word.gsub!(/([A-Z\d]+)([A-Z][a-z])/, '\1_\2')
96
- word.gsub!(/([a-z\d])([A-Z])/, '\1_\2')
100
+ word.gsub!(/([A-Z])(?=[A-Z][a-z])|([a-z\d])(?=[A-Z])/) { ($1 || $2) << "_" }
97
101
  word.tr!("-", "_")
98
102
  word.downcase!
99
103
  word
@@ -105,7 +109,7 @@ module ActiveSupport
105
109
  #
106
110
  # * Applies human inflection rules to the argument.
107
111
  # * Deletes leading underscores, if any.
108
- # * Removes a "_id" suffix if present.
112
+ # * Removes an "_id" suffix if present.
109
113
  # * Replaces underscores with spaces, if any.
110
114
  # * Downcases all words except acronyms.
111
115
  # * Capitalizes the first word.
@@ -119,7 +123,7 @@ module ActiveSupport
119
123
  # humanize('author_id') # => "Author"
120
124
  # humanize('author_id', capitalize: false) # => "author"
121
125
  # humanize('_id') # => "Id"
122
- # humanize('author_id', keep_id_suffix: true) # => "Author Id"
126
+ # humanize('author_id', keep_id_suffix: true) # => "Author id"
123
127
  #
124
128
  # If "SSL" was defined to be an acronym:
125
129
  #
@@ -130,18 +134,22 @@ module ActiveSupport
130
134
 
131
135
  inflections.humans.each { |(rule, replacement)| break if result.sub!(rule, replacement) }
132
136
 
133
- result.sub!(/\A_+/, "")
137
+ result.tr!("_", " ")
138
+ result.lstrip!
134
139
  unless keep_id_suffix
135
- result.sub!(/_id\z/, "")
140
+ result.delete_suffix!(" id")
136
141
  end
137
- result.tr!("_", " ")
138
142
 
139
- result.gsub!(/([a-z\d]*)/i) do |match|
140
- "#{inflections.acronyms[match.downcase] || match.downcase}"
143
+ result.gsub!(/([a-z\d]+)/i) do |match|
144
+ match.downcase!
145
+ inflections.acronyms[match] || match
141
146
  end
142
147
 
143
148
  if capitalize
144
- result.sub!(/\A\w/) { |match| match.upcase }
149
+ result.sub!(/\A\w/) do |match|
150
+ match.upcase!
151
+ match
152
+ end
145
153
  end
146
154
 
147
155
  result
@@ -172,7 +180,7 @@ module ActiveSupport
172
180
  # titleize('raiders_of_the_lost_ark') # => "Raiders Of The Lost Ark"
173
181
  # titleize('string_ending_with_id', keep_id_suffix: true) # => "String Ending With Id"
174
182
  def titleize(word, keep_id_suffix: false)
175
- humanize(underscore(word), keep_id_suffix: keep_id_suffix).gsub(/\b(?<!\w['’`])[a-z]/) do |match|
183
+ humanize(underscore(word), keep_id_suffix: keep_id_suffix).gsub(/\b(?<!\w['’`()])[a-z]/) do |match|
176
184
  match.capitalize
177
185
  end
178
186
  end
@@ -269,34 +277,7 @@ module ActiveSupport
269
277
  # NameError is raised when the name is not in CamelCase or the constant is
270
278
  # unknown.
271
279
  def constantize(camel_cased_word)
272
- names = camel_cased_word.split("::")
273
-
274
- # Trigger a built-in NameError exception including the ill-formed constant in the message.
275
- Object.const_get(camel_cased_word) if names.empty?
276
-
277
- # Remove the first blank element in case of '::ClassName' notation.
278
- names.shift if names.size > 1 && names.first.empty?
279
-
280
- names.inject(Object) do |constant, name|
281
- if constant == Object
282
- constant.const_get(name)
283
- else
284
- candidate = constant.const_get(name)
285
- next candidate if constant.const_defined?(name, false)
286
- next candidate unless Object.const_defined?(name)
287
-
288
- # Go down the ancestors to check if it is owned directly. The check
289
- # stops when we reach Object or the end of ancestors tree.
290
- constant = constant.ancestors.inject(constant) do |const, ancestor|
291
- break const if ancestor == Object
292
- break ancestor if ancestor.const_defined?(name, false)
293
- const
294
- end
295
-
296
- # owner is in Object, so raise
297
- constant.const_get(name, false)
298
- end
299
- end
280
+ Object.const_get(camel_cased_word)
300
281
  end
301
282
 
302
283
  # Tries to find a constant with the name specified in the argument string.
@@ -326,10 +307,9 @@ module ActiveSupport
326
307
  rescue NameError => e
327
308
  raise if e.name && !(camel_cased_word.to_s.split("::").include?(e.name.to_s) ||
328
309
  e.name.to_s == camel_cased_word.to_s)
329
- rescue ArgumentError => e
330
- raise unless /not missing constant #{const_regexp(camel_cased_word)}!$/.match?(e.message)
331
310
  rescue LoadError => e
332
- raise unless /Unable to autoload constant #{const_regexp(camel_cased_word)}/.match?(e.message)
311
+ message = e.respond_to?(:original_message) ? e.original_message : e.message
312
+ raise unless /Unable to autoload constant #{const_regexp(camel_cased_word)}/.match?(message)
333
313
  end
334
314
 
335
315
  # Returns the suffix that should be added to a number to denote the position
@@ -371,7 +351,7 @@ module ActiveSupport
371
351
 
372
352
  last = parts.pop
373
353
 
374
- parts.reverse.inject(last) do |acc, part|
354
+ parts.reverse!.inject(last) do |acc, part|
375
355
  part.empty? ? acc : "#{part}(::#{acc})?"
376
356
  end
377
357
  end
@@ -5,6 +5,8 @@ require "active_support/i18n"
5
5
 
6
6
  module ActiveSupport
7
7
  module Inflector
8
+ ALLOWED_ENCODINGS_FOR_TRANSLITERATE = [Encoding::UTF_8, Encoding::US_ASCII, Encoding::GB18030].freeze
9
+
8
10
  # Replaces non-ASCII characters with an ASCII approximation, or if none
9
11
  # exists, a replacement character which defaults to "?".
10
12
  #
@@ -57,14 +59,12 @@ module ActiveSupport
57
59
  # transliterate('Jürgen', locale: :de)
58
60
  # # => "Juergen"
59
61
  #
60
- # Transliteration is restricted to UTF-8, US-ASCII and GB18030 strings
62
+ # Transliteration is restricted to UTF-8, US-ASCII, and GB18030 strings.
61
63
  # Other encodings will raise an ArgumentError.
62
64
  def transliterate(string, replacement = "?", locale: nil)
63
65
  string = string.dup if string.frozen?
64
66
  raise ArgumentError, "Can only transliterate strings. Received #{string.class.name}" unless string.is_a?(String)
65
-
66
- allowed_encodings = [Encoding::UTF_8, Encoding::US_ASCII, Encoding::GB18030]
67
- raise ArgumentError, "Can not transliterate strings with #{string.encoding} encoding" unless allowed_encodings.include?(string.encoding)
67
+ raise ArgumentError, "Cannot transliterate strings with #{string.encoding} encoding" unless ALLOWED_ENCODINGS_FOR_TRANSLITERATE.include?(string.encoding)
68
68
 
69
69
  input_encoding = string.encoding
70
70
 
@@ -117,7 +117,7 @@ module ActiveSupport
117
117
  # If the optional parameter +locale+ is specified,
118
118
  # the word will be parameterized as a word of that language.
119
119
  # By default, this parameter is set to <tt>nil</tt> and it will use
120
- # the configured <tt>I18n.locale<tt>.
120
+ # the configured <tt>I18n.locale</tt>.
121
121
  def parameterize(string, separator: "-", preserve_case: false, locale: nil)
122
122
  # Replace accented chars with their ASCII equivalents.
123
123
  parameterized_string = transliterate(string, locale: locale)
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "fiber"
4
+
5
+ module ActiveSupport
6
+ module IsolatedExecutionState # :nodoc:
7
+ @isolation_level = :thread
8
+
9
+ Thread.attr_accessor :active_support_execution_state
10
+ Fiber.attr_accessor :active_support_execution_state
11
+
12
+ class << self
13
+ attr_reader :isolation_level
14
+
15
+ def isolation_level=(level)
16
+ unless %i(thread fiber).include?(level)
17
+ raise ArgumentError, "isolation_level must be `:thread` or `:fiber`, got: `#{level.inspect}`"
18
+ end
19
+
20
+ if level != isolation_level
21
+ clear
22
+ singleton_class.alias_method(:current, "current_#{level}")
23
+ singleton_class.send(:private, :current)
24
+ @isolation_level = level
25
+ end
26
+ end
27
+
28
+ def unique_id
29
+ self[:__id__] ||= Object.new
30
+ end
31
+
32
+ def [](key)
33
+ current[key]
34
+ end
35
+
36
+ def []=(key, value)
37
+ current[key] = value
38
+ end
39
+
40
+ def key?(key)
41
+ current.key?(key)
42
+ end
43
+
44
+ def delete(key)
45
+ current.delete(key)
46
+ end
47
+
48
+ def clear
49
+ current.clear
50
+ end
51
+
52
+ def share_with(other)
53
+ # Action Controller streaming spawns a new thread and copy thread locals.
54
+ # We do the same here for backward compatibility, but this is very much a hack
55
+ # and streaming should be rethought.
56
+ context = @isolation_level == :thread ? Thread.current : Fiber.current
57
+ context.active_support_execution_state = other.active_support_execution_state.dup
58
+ end
59
+
60
+ private
61
+ def current_thread
62
+ Thread.current.active_support_execution_state ||= {}
63
+ end
64
+
65
+ def current_fiber
66
+ Fiber.current.active_support_execution_state ||= {}
67
+ end
68
+
69
+ alias_method :current, :current_thread
70
+ end
71
+ end
72
+ end
@@ -10,8 +10,8 @@ module ActiveSupport
10
10
 
11
11
  module JSON
12
12
  # matches YAML-formatted dates
13
- DATE_REGEX = /^\d{4}-\d{2}-\d{2}$/
14
- DATETIME_REGEX = /^(?:\d{4}-\d{2}-\d{2}|\d{4}-\d{1,2}-\d{1,2}[T \t]+\d{1,2}:\d{2}:\d{2}(\.[0-9]*)?(([ \t]*)Z|[-+]\d{2}?(:\d{2})?)?)$/
13
+ DATE_REGEX = /\A\d{4}-\d{2}-\d{2}\z/
14
+ DATETIME_REGEX = /\A(?:\d{4}-\d{2}-\d{2}|\d{4}-\d{1,2}-\d{1,2}[T \t]+\d{1,2}:\d{2}:\d{2}(\.[0-9]*)?(([ \t]*)Z|[-+]\d{2}?(:\d{2})?)?)\z/
15
15
 
16
16
  class << self
17
17
  # Parses a JSON string (JavaScript Object Notation) into a hash.
@@ -63,8 +63,8 @@ module ActiveSupport
63
63
  when Array
64
64
  data.map! { |d| convert_dates_from(d) }
65
65
  when Hash
66
- data.each do |key, value|
67
- data[key] = convert_dates_from(value)
66
+ data.transform_values! do |value|
67
+ convert_dates_from(value)
68
68
  end
69
69
  else
70
70
  data