activesupport 7.2.2.1 → 8.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (137) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +422 -145
  3. data/README.rdoc +1 -1
  4. data/lib/active_support/backtrace_cleaner.rb +73 -2
  5. data/lib/active_support/benchmark.rb +21 -0
  6. data/lib/active_support/benchmarkable.rb +3 -2
  7. data/lib/active_support/broadcast_logger.rb +61 -74
  8. data/lib/active_support/cache/file_store.rb +14 -4
  9. data/lib/active_support/cache/mem_cache_store.rb +30 -29
  10. data/lib/active_support/cache/memory_store.rb +11 -5
  11. data/lib/active_support/cache/null_store.rb +2 -2
  12. data/lib/active_support/cache/redis_cache_store.rb +43 -34
  13. data/lib/active_support/cache/strategy/local_cache.rb +72 -27
  14. data/lib/active_support/cache/strategy/local_cache_middleware.rb +7 -7
  15. data/lib/active_support/cache.rb +88 -20
  16. data/lib/active_support/callbacks.rb +28 -13
  17. data/lib/active_support/class_attribute.rb +33 -0
  18. data/lib/active_support/code_generator.rb +9 -0
  19. data/lib/active_support/concurrency/load_interlock_aware_monitor.rb +8 -62
  20. data/lib/active_support/concurrency/share_lock.rb +0 -1
  21. data/lib/active_support/concurrency/thread_monitor.rb +55 -0
  22. data/lib/active_support/configurable.rb +34 -0
  23. data/lib/active_support/configuration_file.rb +15 -6
  24. data/lib/active_support/continuous_integration.rb +145 -0
  25. data/lib/active_support/core_ext/array/conversions.rb +3 -3
  26. data/lib/active_support/core_ext/array.rb +7 -7
  27. data/lib/active_support/core_ext/benchmark.rb +0 -15
  28. data/lib/active_support/core_ext/big_decimal.rb +1 -1
  29. data/lib/active_support/core_ext/class/attribute.rb +26 -20
  30. data/lib/active_support/core_ext/class.rb +2 -2
  31. data/lib/active_support/core_ext/date/conversions.rb +2 -0
  32. data/lib/active_support/core_ext/date.rb +5 -5
  33. data/lib/active_support/core_ext/date_and_time/compatibility.rb +0 -35
  34. data/lib/active_support/core_ext/date_time/compatibility.rb +3 -5
  35. data/lib/active_support/core_ext/date_time/conversions.rb +4 -2
  36. data/lib/active_support/core_ext/date_time.rb +5 -5
  37. data/lib/active_support/core_ext/digest.rb +1 -1
  38. data/lib/active_support/core_ext/enumerable.rb +25 -8
  39. data/lib/active_support/core_ext/erb/util.rb +5 -5
  40. data/lib/active_support/core_ext/file.rb +1 -1
  41. data/lib/active_support/core_ext/hash/deep_merge.rb +1 -0
  42. data/lib/active_support/core_ext/hash/except.rb +0 -12
  43. data/lib/active_support/core_ext/hash.rb +8 -8
  44. data/lib/active_support/core_ext/integer.rb +3 -3
  45. data/lib/active_support/core_ext/kernel.rb +3 -3
  46. data/lib/active_support/core_ext/module/attr_internal.rb +3 -4
  47. data/lib/active_support/core_ext/module/introspection.rb +3 -0
  48. data/lib/active_support/core_ext/module.rb +11 -11
  49. data/lib/active_support/core_ext/numeric.rb +3 -3
  50. data/lib/active_support/core_ext/object/json.rb +24 -11
  51. data/lib/active_support/core_ext/object/to_query.rb +7 -1
  52. data/lib/active_support/core_ext/object/try.rb +2 -2
  53. data/lib/active_support/core_ext/object.rb +13 -13
  54. data/lib/active_support/core_ext/pathname.rb +2 -2
  55. data/lib/active_support/core_ext/range/overlap.rb +3 -3
  56. data/lib/active_support/core_ext/range/sole.rb +17 -0
  57. data/lib/active_support/core_ext/range.rb +4 -4
  58. data/lib/active_support/core_ext/securerandom.rb +24 -8
  59. data/lib/active_support/core_ext/string/filters.rb +3 -3
  60. data/lib/active_support/core_ext/string/inflections.rb +1 -1
  61. data/lib/active_support/core_ext/string/multibyte.rb +12 -3
  62. data/lib/active_support/core_ext/string/output_safety.rb +29 -13
  63. data/lib/active_support/core_ext/string.rb +13 -13
  64. data/lib/active_support/core_ext/symbol.rb +1 -1
  65. data/lib/active_support/core_ext/thread/backtrace/location.rb +2 -7
  66. data/lib/active_support/core_ext/time/calculations.rb +7 -2
  67. data/lib/active_support/core_ext/time/compatibility.rb +2 -19
  68. data/lib/active_support/core_ext/time/conversions.rb +2 -0
  69. data/lib/active_support/core_ext/time.rb +5 -5
  70. data/lib/active_support/current_attributes/test_helper.rb +2 -2
  71. data/lib/active_support/current_attributes.rb +27 -17
  72. data/lib/active_support/delegation.rb +25 -44
  73. data/lib/active_support/dependencies/interlock.rb +11 -5
  74. data/lib/active_support/dependencies.rb +6 -2
  75. data/lib/active_support/deprecation/reporting.rb +4 -21
  76. data/lib/active_support/deprecation.rb +1 -1
  77. data/lib/active_support/duration.rb +14 -10
  78. data/lib/active_support/editor.rb +70 -0
  79. data/lib/active_support/encrypted_configuration.rb +20 -2
  80. data/lib/active_support/error_reporter.rb +81 -4
  81. data/lib/active_support/event_reporter/test_helper.rb +32 -0
  82. data/lib/active_support/event_reporter.rb +592 -0
  83. data/lib/active_support/evented_file_update_checker.rb +5 -2
  84. data/lib/active_support/execution_context.rb +75 -7
  85. data/lib/active_support/execution_wrapper.rb +1 -1
  86. data/lib/active_support/file_update_checker.rb +8 -6
  87. data/lib/active_support/gem_version.rb +4 -4
  88. data/lib/active_support/gzip.rb +1 -0
  89. data/lib/active_support/hash_with_indifferent_access.rb +61 -38
  90. data/lib/active_support/i18n_railtie.rb +19 -11
  91. data/lib/active_support/inflector/inflections.rb +34 -16
  92. data/lib/active_support/inflector/methods.rb +3 -3
  93. data/lib/active_support/inflector/transliterate.rb +6 -8
  94. data/lib/active_support/isolated_execution_state.rb +17 -17
  95. data/lib/active_support/json/decoding.rb +6 -4
  96. data/lib/active_support/json/encoding.rb +159 -21
  97. data/lib/active_support/lazy_load_hooks.rb +1 -1
  98. data/lib/active_support/log_subscriber.rb +2 -6
  99. data/lib/active_support/logger_thread_safe_level.rb +6 -3
  100. data/lib/active_support/message_encryptors.rb +54 -2
  101. data/lib/active_support/message_pack/extensions.rb +6 -1
  102. data/lib/active_support/message_verifier.rb +9 -0
  103. data/lib/active_support/message_verifiers.rb +57 -3
  104. data/lib/active_support/messages/rotation_coordinator.rb +9 -0
  105. data/lib/active_support/messages/rotator.rb +10 -0
  106. data/lib/active_support/multibyte/chars.rb +12 -2
  107. data/lib/active_support/multibyte.rb +4 -0
  108. data/lib/active_support/notifications/fanout.rb +64 -43
  109. data/lib/active_support/notifications/instrumenter.rb +1 -1
  110. data/lib/active_support/number_helper/number_converter.rb +1 -1
  111. data/lib/active_support/number_helper/number_to_delimited_converter.rb +17 -2
  112. data/lib/active_support/number_helper.rb +22 -0
  113. data/lib/active_support/railtie.rb +32 -9
  114. data/lib/active_support/structured_event_subscriber.rb +99 -0
  115. data/lib/active_support/subscriber.rb +0 -5
  116. data/lib/active_support/syntax_error_proxy.rb +7 -0
  117. data/lib/active_support/tagged_logging.rb +5 -0
  118. data/lib/active_support/test_case.rb +67 -6
  119. data/lib/active_support/testing/assertions.rb +118 -27
  120. data/lib/active_support/testing/autorun.rb +5 -0
  121. data/lib/active_support/testing/error_reporter_assertions.rb +17 -0
  122. data/lib/active_support/testing/event_reporter_assertions.rb +227 -0
  123. data/lib/active_support/testing/isolation.rb +0 -2
  124. data/lib/active_support/testing/notification_assertions.rb +92 -0
  125. data/lib/active_support/testing/parallelization/server.rb +15 -2
  126. data/lib/active_support/testing/parallelization/worker.rb +9 -3
  127. data/lib/active_support/testing/parallelization.rb +25 -1
  128. data/lib/active_support/testing/tests_without_assertions.rb +1 -1
  129. data/lib/active_support/testing/time_helpers.rb +9 -4
  130. data/lib/active_support/time_with_zone.rb +36 -23
  131. data/lib/active_support/values/time_zone.rb +19 -10
  132. data/lib/active_support/xml_mini.rb +3 -2
  133. data/lib/active_support.rb +21 -9
  134. metadata +35 -16
  135. data/lib/active_support/core_ext/range/each.rb +0 -24
  136. data/lib/active_support/proxy_object.rb +0 -20
  137. data/lib/active_support/testing/strict_warnings.rb +0 -43
@@ -2,8 +2,48 @@
2
2
 
3
3
  module ActiveSupport
4
4
  module ExecutionContext # :nodoc:
5
+ class Record
6
+ attr_reader :store, :current_attributes_instances
7
+
8
+ def initialize
9
+ @store = {}
10
+ @current_attributes_instances = {}
11
+ @stack = []
12
+ end
13
+
14
+ def push
15
+ @stack << @store << @current_attributes_instances
16
+ @store = {}
17
+ @current_attributes_instances = {}
18
+ self
19
+ end
20
+
21
+ def pop
22
+ @current_attributes_instances = @stack.pop
23
+ @store = @stack.pop
24
+ self
25
+ end
26
+
27
+ def flush
28
+ @stack = Array.new(@stack.size) { {} }
29
+ @store = {}
30
+ @current_attributes_instances = {}
31
+ self
32
+ end
33
+ end
34
+
5
35
  @after_change_callbacks = []
36
+
37
+ # Execution context nesting should only legitimately happen during test
38
+ # because the test case itself is wrapped in an executor, and it might call
39
+ # into a controller or job which should be executed with their own fresh context.
40
+ # However in production this should never happen, and for extra safety we make sure to
41
+ # fully clear the state at the end of the request or job cycle.
42
+ @nestable = false
43
+
6
44
  class << self
45
+ attr_accessor :nestable
46
+
7
47
  def after_change(&block)
8
48
  @after_change_callbacks << block
9
49
  end
@@ -14,9 +54,11 @@ module ActiveSupport
14
54
  options.symbolize_keys!
15
55
  keys = options.keys
16
56
 
17
- store = self.store
57
+ store = record.store
18
58
 
19
- previous_context = keys.zip(store.values_at(*keys)).to_h
59
+ previous_context = if block_given?
60
+ keys.zip(store.values_at(*keys)).to_h
61
+ end
20
62
 
21
63
  store.merge!(options)
22
64
  @after_change_callbacks.each(&:call)
@@ -32,21 +74,47 @@ module ActiveSupport
32
74
  end
33
75
 
34
76
  def []=(key, value)
35
- store[key.to_sym] = value
77
+ record.store[key.to_sym] = value
36
78
  @after_change_callbacks.each(&:call)
37
79
  end
38
80
 
39
81
  def to_h
40
- store.dup
82
+ record.store.dup
83
+ end
84
+
85
+ def push
86
+ if @nestable
87
+ record.push
88
+ else
89
+ clear
90
+ end
91
+ self
92
+ end
93
+
94
+ def pop
95
+ if @nestable
96
+ record.pop
97
+ else
98
+ clear
99
+ end
100
+ self
41
101
  end
42
102
 
43
103
  def clear
44
- store.clear
104
+ IsolatedExecutionState[:active_support_execution_context] = nil
105
+ end
106
+
107
+ def flush
108
+ record.flush
109
+ end
110
+
111
+ def current_attributes_instances
112
+ record.current_attributes_instances
45
113
  end
46
114
 
47
115
  private
48
- def store
49
- IsolatedExecutionState[:active_support_execution_context] ||= {}
116
+ def record
117
+ IsolatedExecutionState[:active_support_execution_context] ||= Record.new
50
118
  end
51
119
  end
52
120
  end
@@ -89,7 +89,7 @@ module ActiveSupport
89
89
  instance = run!
90
90
  begin
91
91
  yield
92
- rescue => error
92
+ rescue Exception => error
93
93
  error_reporter&.report(error, handled: false, source: source)
94
94
  raise
95
95
  ensure
@@ -46,8 +46,11 @@ module ActiveSupport
46
46
  raise ArgumentError, "A block is required to initialize a FileUpdateChecker"
47
47
  end
48
48
 
49
- @files = files.freeze
50
- @glob = compile_glob(dirs)
49
+ gem_paths = Gem.path
50
+ @files = files.reject { |file| File.expand_path(file).start_with?(*gem_paths) }.freeze
51
+
52
+ @globs = compile_glob(dirs)&.reject { |dir| dir.start_with?(*gem_paths) }
53
+
51
54
  @block = block
52
55
 
53
56
  @watched = nil
@@ -103,7 +106,7 @@ module ActiveSupport
103
106
  def watched
104
107
  @watched || begin
105
108
  all = @files.select { |f| File.exist?(f) }
106
- all.concat(Dir[@glob]) if @glob
109
+ all.concat(Dir[*@globs]) if @globs
107
110
  all.tap(&:uniq!)
108
111
  end
109
112
  end
@@ -120,7 +123,7 @@ module ActiveSupport
120
123
  # healthy to consider this edge case because with mtimes in the future
121
124
  # reloading is not triggered.
122
125
  def max_mtime(paths)
123
- time_now = Time.now
126
+ time_now = Time.at(0, Process.clock_gettime(Process::CLOCK_REALTIME, :nanosecond), :nanosecond)
124
127
  max_mtime = nil
125
128
 
126
129
  # Time comparisons are performed with #compare_without_coercion because
@@ -145,10 +148,9 @@ module ActiveSupport
145
148
  hash.freeze # Freeze so changes aren't accidentally pushed
146
149
  return if hash.empty?
147
150
 
148
- globs = hash.map do |key, value|
151
+ hash.map do |key, value|
149
152
  "#{escape(key)}/**/*#{compile_ext(value)}"
150
153
  end
151
- "{#{globs.join(",")}}"
152
154
  end
153
155
 
154
156
  def escape(key)
@@ -7,10 +7,10 @@ module ActiveSupport
7
7
  end
8
8
 
9
9
  module VERSION
10
- MAJOR = 7
11
- MINOR = 2
12
- TINY = 2
13
- PRE = "1"
10
+ MAJOR = 8
11
+ MINOR = 1
12
+ TINY = 3
13
+ PRE = nil
14
14
 
15
15
  STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
16
16
  end
@@ -32,6 +32,7 @@ module ActiveSupport
32
32
  def self.compress(source, level = Zlib::DEFAULT_COMPRESSION, strategy = Zlib::DEFAULT_STRATEGY)
33
33
  output = Stream.new
34
34
  gz = Zlib::GzipWriter.new(output, level, strategy)
35
+ gz.mtime = 0
35
36
  gz.write(source)
36
37
  gz.close
37
38
  output.string
@@ -68,15 +68,15 @@ module ActiveSupport
68
68
  end
69
69
 
70
70
  def initialize(constructor = nil)
71
- if constructor.respond_to?(:to_hash)
71
+ if constructor.nil?
72
+ super()
73
+ elsif constructor.respond_to?(:to_hash)
72
74
  super()
73
75
  update(constructor)
74
76
 
75
77
  hash = constructor.is_a?(Hash) ? constructor : constructor.to_hash
76
78
  self.default = hash.default if hash.default
77
79
  self.default_proc = hash.default_proc if hash.default_proc
78
- elsif constructor.nil?
79
- super()
80
80
  else
81
81
  super(constructor)
82
82
  end
@@ -95,11 +95,27 @@ module ActiveSupport
95
95
  # hash[:key] = 'value'
96
96
  #
97
97
  # This value can be later fetched using either +:key+ or <tt>'key'</tt>.
98
+ #
99
+ # If the value is a Hash or contains one or multiple Hashes, they will be
100
+ # converted to +HashWithIndifferentAccess+.
98
101
  def []=(key, value)
99
102
  regular_writer(convert_key(key), convert_value(value, conversion: :assignment))
100
103
  end
101
104
 
102
- alias_method :store, :[]=
105
+ # Assigns a new value to the hash:
106
+ #
107
+ # hash = ActiveSupport::HashWithIndifferentAccess.new
108
+ # hash[:key] = 'value'
109
+ #
110
+ # This value can be later fetched using either +:key+ or <tt>'key'</tt>.
111
+ #
112
+ # If the value is a Hash or contains one or multiple Hashes, they will be
113
+ # converted to +HashWithIndifferentAccess+. unless `convert_value: false`
114
+ # is set.
115
+ def store(key, value, convert_value: true)
116
+ value = convert_value(value, conversion: :assignment) if convert_value
117
+ regular_writer(convert_key(key), value)
118
+ end
103
119
 
104
120
  # Updates the receiver in-place, merging in the hashes passed as arguments:
105
121
  #
@@ -262,9 +278,7 @@ module ActiveSupport
262
278
  # hash[:a][:c] # => "c"
263
279
  # dup[:a][:c] # => "c"
264
280
  def dup
265
- self.class.new(self).tap do |new_hash|
266
- set_defaults(new_hash)
267
- end
281
+ copy_defaults(self.class.new(self))
268
282
  end
269
283
 
270
284
  # This method has the same semantics of +update+, except it does not
@@ -281,13 +295,13 @@ module ActiveSupport
281
295
  # hash['a'] = nil
282
296
  # hash.reverse_merge(a: 0, b: 1) # => {"a"=>nil, "b"=>1}
283
297
  def reverse_merge(other_hash)
284
- super(self.class.new(other_hash))
298
+ super(cast(other_hash))
285
299
  end
286
300
  alias_method :with_defaults, :reverse_merge
287
301
 
288
302
  # Same semantics as +reverse_merge+ but modifies the receiver in-place.
289
303
  def reverse_merge!(other_hash)
290
- super(self.class.new(other_hash))
304
+ super(cast(other_hash))
291
305
  end
292
306
  alias_method :with_defaults!, :reverse_merge!
293
307
 
@@ -296,7 +310,7 @@ module ActiveSupport
296
310
  # h = { "a" => 100, "b" => 200 }
297
311
  # h.replace({ "c" => 300, "d" => 400 }) # => {"c"=>300, "d"=>400}
298
312
  def replace(other_hash)
299
- super(self.class.new(other_hash))
313
+ super(cast(other_hash))
300
314
  end
301
315
 
302
316
  # Removes the specified key from the hash.
@@ -313,10 +327,6 @@ module ActiveSupport
313
327
  end
314
328
  alias_method :without, :except
315
329
 
316
- def stringify_keys!; self end
317
- def deep_stringify_keys!; self end
318
- def stringify_keys; dup end
319
- def deep_stringify_keys; dup end
320
330
  undef :symbolize_keys!
321
331
  undef :deep_symbolize_keys!
322
332
  def symbolize_keys; to_hash.symbolize_keys! end
@@ -342,21 +352,26 @@ module ActiveSupport
342
352
  NOT_GIVEN = Object.new # :nodoc:
343
353
 
344
354
  def transform_keys(hash = NOT_GIVEN, &block)
345
- return to_enum(:transform_keys) if NOT_GIVEN.equal?(hash) && !block_given?
346
- dup.tap { |h| h.transform_keys!(hash, &block) }
355
+ if NOT_GIVEN.equal?(hash)
356
+ if block_given?
357
+ self.class.new(super(&block))
358
+ else
359
+ to_enum(:transform_keys)
360
+ end
361
+ else
362
+ self.class.new(super)
363
+ end
347
364
  end
348
365
 
349
366
  def transform_keys!(hash = NOT_GIVEN, &block)
350
- return to_enum(:transform_keys!) if NOT_GIVEN.equal?(hash) && !block_given?
351
-
352
- if hash.nil?
353
- super
354
- elsif NOT_GIVEN.equal?(hash)
355
- keys.each { |key| self[yield(key)] = delete(key) }
356
- elsif block_given?
357
- keys.each { |key| self[hash[key] || yield(key)] = delete(key) }
367
+ if NOT_GIVEN.equal?(hash)
368
+ if block_given?
369
+ replace(copy_defaults(transform_keys(&block)))
370
+ else
371
+ return to_enum(:transform_keys!)
372
+ end
358
373
  else
359
- keys.each { |key| self[hash[key] || key] = delete(key) }
374
+ replace(copy_defaults(transform_keys(hash, &block)))
360
375
  end
361
376
 
362
377
  self
@@ -378,13 +393,9 @@ module ActiveSupport
378
393
 
379
394
  # Convert to a regular hash with string keys.
380
395
  def to_hash
381
- _new_hash = Hash.new
382
- set_defaults(_new_hash)
383
-
384
- each do |key, value|
385
- _new_hash[key] = convert_value(value, conversion: :to_hash)
386
- end
387
- _new_hash
396
+ copy = Hash[self]
397
+ copy.transform_values! { |v| convert_value_to_hash(v) }
398
+ copy_defaults(copy)
388
399
  end
389
400
 
390
401
  def to_proc
@@ -392,17 +403,17 @@ module ActiveSupport
392
403
  end
393
404
 
394
405
  private
406
+ def cast(other)
407
+ self.class === other ? other : self.class.new(other)
408
+ end
409
+
395
410
  def convert_key(key)
396
411
  Symbol === key ? key.name : key
397
412
  end
398
413
 
399
414
  def convert_value(value, conversion: nil)
400
415
  if value.is_a? Hash
401
- if conversion == :to_hash
402
- value.to_hash
403
- else
404
- value.nested_under_indifferent_access
405
- end
416
+ value.nested_under_indifferent_access
406
417
  elsif value.is_a?(Array)
407
418
  if conversion != :assignment || value.frozen?
408
419
  value = value.dup
@@ -413,12 +424,24 @@ module ActiveSupport
413
424
  end
414
425
  end
415
426
 
416
- def set_defaults(target)
427
+ def convert_value_to_hash(value)
428
+ if value.is_a? Hash
429
+ value.to_hash
430
+ elsif value.is_a?(Array)
431
+ value.map { |e| convert_value_to_hash(e) }
432
+ else
433
+ value
434
+ end
435
+ end
436
+
437
+
438
+ def copy_defaults(target)
417
439
  if default_proc
418
440
  target.default_proc = default_proc.dup
419
441
  else
420
442
  target.default = default
421
443
  end
444
+ target
422
445
  end
423
446
 
424
447
  def update_with_single_argument(other_hash, block)
@@ -2,6 +2,7 @@
2
2
 
3
3
  require "active_support"
4
4
  require "active_support/core_ext/array/wrap"
5
+ require "rails/railtie"
5
6
 
6
7
  # :enddoc:
7
8
 
@@ -14,15 +15,18 @@ module I18n
14
15
 
15
16
  config.eager_load_namespaces << I18n
16
17
 
17
- # Set the i18n configuration after initialization since a lot of
18
- # configuration is still usually done in application initializers.
19
- config.after_initialize do |app|
18
+ # Make sure i18n is ready before eager loading, in case any eager loaded
19
+ # code needs it.
20
+ config.before_eager_load do |app|
20
21
  I18n::Railtie.initialize_i18n(app)
21
22
  end
22
23
 
23
- # Trigger i18n config before any eager loading has happened
24
- # so it's ready if any classes require it when eager loaded.
25
- config.before_eager_load do |app|
24
+ # i18n initialization needs to run after application initialization, since
25
+ # initializers may configure i18n.
26
+ #
27
+ # If the application eager loaded, this was done on before_eager_load. The
28
+ # hook is still OK, though, because initialize_i18n is idempotent.
29
+ config.after_initialize do |app|
26
30
  I18n::Railtie.initialize_i18n(app)
27
31
  end
28
32
 
@@ -49,7 +53,8 @@ module I18n
49
53
  when :load_path
50
54
  I18n.load_path += value
51
55
  when :raise_on_missing_translations
52
- setup_raise_on_missing_translations_config(app)
56
+ strict = value == :strict
57
+ setup_raise_on_missing_translations_config(app, strict)
53
58
  else
54
59
  I18n.public_send("#{setting}=", value)
55
60
  end
@@ -62,8 +67,8 @@ module I18n
62
67
 
63
68
  if app.config.reloading_enabled?
64
69
  directories = watched_dirs_with_extensions(reloadable_paths)
65
- reloader = app.config.file_watcher.new(I18n.load_path.dup, directories) do
66
- I18n.load_path.keep_if { |p| File.exist?(p) }
70
+ reloader = app.config.file_watcher.new(I18n.load_path, directories) do
71
+ I18n.load_path.delete_if { |path| path.to_s.start_with?(Rails.root.to_s) && !File.exist?(path) }
67
72
  I18n.load_path |= reloadable_paths.flat_map(&:existent)
68
73
  end
69
74
 
@@ -71,17 +76,20 @@ module I18n
71
76
  app.reloader.to_run do
72
77
  reloader.execute_if_updated { require_unload_lock! }
73
78
  end
74
- reloader.execute
75
79
  end
76
80
 
77
81
  @i18n_inited = true
78
82
  end
79
83
 
80
- def self.setup_raise_on_missing_translations_config(app)
84
+ def self.setup_raise_on_missing_translations_config(app, strict)
81
85
  ActiveSupport.on_load(:action_view) do
82
86
  ActionView::Helpers::TranslationHelper.raise_on_missing_translations = app.config.i18n.raise_on_missing_translations
83
87
  end
84
88
 
89
+ ActiveSupport.on_load(:active_model_translation) do
90
+ ActiveModel::Translation.raise_on_missing_translations = app.config.i18n.raise_on_missing_translations if strict
91
+ end
92
+
85
93
  if app.config.i18n.raise_on_missing_translations &&
86
94
  I18n.exception_handler.is_a?(I18n::ExceptionHandler) # Only override the i18n gem's default exception handler.
87
95
 
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "concurrent/map"
4
+ require "active_support/core_ext/module/delegation"
4
5
  require "active_support/i18n"
5
6
 
6
7
  module ActiveSupport
@@ -29,45 +30,61 @@ module ActiveSupport
29
30
  # before any of the rules that may already have been loaded.
30
31
  class Inflections
31
32
  @__instance__ = Concurrent::Map.new
33
+ @__en_instance__ = nil
34
+
35
+ class Uncountables # :nodoc:
36
+ include Enumerable
37
+
38
+ delegate :each, :pop, :empty?, :to_s, :==, :to_a, :to_ary, to: :@members
32
39
 
33
- class Uncountables < Array
34
40
  def initialize
35
- @regex_array = []
36
- super
41
+ @members = []
42
+ @pattern = nil
37
43
  end
38
44
 
39
45
  def delete(entry)
40
- super entry
41
- @regex_array.delete(to_regex(entry))
46
+ @members.delete(entry)
47
+ @pattern = nil
48
+ end
49
+
50
+ def <<(word)
51
+ word = word.downcase
52
+ @members << word
53
+ @pattern = nil
54
+ self
42
55
  end
43
56
 
44
- def <<(*word)
45
- add(word)
57
+ def flatten
58
+ @members.dup
46
59
  end
47
60
 
48
61
  def add(words)
49
62
  words = words.flatten.map(&:downcase)
50
- concat(words)
51
- @regex_array += words.map { |word| to_regex(word) }
63
+ @members.concat(words)
64
+ @pattern = nil
52
65
  self
53
66
  end
54
67
 
55
68
  def uncountable?(str)
56
- @regex_array.any? { |regex| regex.match? str }
57
- end
58
-
59
- private
60
- def to_regex(string)
61
- /\b#{::Regexp.escape(string)}\Z/i
69
+ if @pattern.nil?
70
+ members_pattern = Regexp.union(@members.map { |w| /#{Regexp.escape(w)}/i })
71
+ @pattern = /\b#{members_pattern}\Z/i
62
72
  end
73
+ @pattern.match?(str)
74
+ end
63
75
  end
64
76
 
65
77
  def self.instance(locale = :en)
78
+ return @__en_instance__ ||= new if locale == :en
79
+
66
80
  @__instance__[locale] ||= new
67
81
  end
68
82
 
69
83
  def self.instance_or_fallback(locale)
84
+ return @__en_instance__ ||= new if locale == :en
85
+
70
86
  I18n.fallbacks[locale].each do |k|
87
+ return @__en_instance__ if k == :en && @__en_instance__
71
88
  return @__instance__[k] if @__instance__.key?(k)
72
89
  end
73
90
  instance(locale)
@@ -248,7 +265,8 @@ module ActiveSupport
248
265
 
249
266
  private
250
267
  def define_acronym_regex_patterns
251
- @acronym_regex = @acronyms.empty? ? /(?=a)b/ : /#{@acronyms.values.join("|")}/
268
+ sorted_acronyms = @acronyms.empty? ? [] : @acronyms.values.sort_by { |a| -a.length }
269
+ @acronym_regex = sorted_acronyms.empty? ? /(?=a)b/ : /#{sorted_acronyms.join("|")}/
252
270
  @acronyms_camelize_regex = /^(?:#{@acronym_regex}(?=\b|[A-Z_])|\w)/
253
271
  @acronyms_underscore_regex = /(?:(?<=([A-Za-z\d]))|\b)(#{@acronym_regex})(?=\b|[^a-z])/
254
272
  end
@@ -119,7 +119,7 @@ module ActiveSupport
119
119
  # The capitalization of the first word can be turned off by setting the
120
120
  # +:capitalize+ option to false (default is true).
121
121
  #
122
- # The trailing '_id' can be kept and capitalized by setting the
122
+ # The trailing '_id' can be kept by setting the
123
123
  # optional parameter +keep_id_suffix+ to true (default is false).
124
124
  #
125
125
  # humanize('employee_salary') # => "Employee salary"
@@ -143,13 +143,13 @@ module ActiveSupport
143
143
  result.delete_suffix!(" id")
144
144
  end
145
145
 
146
- result.gsub!(/([a-z\d]+)/i) do |match|
146
+ result.gsub!(/([[[:alpha:]]\d]+)/i) do |match|
147
147
  match.downcase!
148
148
  inflections.acronyms[match] || match
149
149
  end
150
150
 
151
151
  if capitalize
152
- result.sub!(/\A\w/) do |match|
152
+ result.sub!(/\A[[:alpha:]]/) do |match|
153
153
  match.upcase!
154
154
  match
155
155
  end
@@ -128,18 +128,16 @@ module ActiveSupport
128
128
  parameterized_string.gsub!(/[^a-z0-9\-_]+/i, separator)
129
129
 
130
130
  unless separator.nil? || separator.empty?
131
- if separator == "-"
132
- re_duplicate_separator = /-{2,}/
133
- re_leading_trailing_separator = /^-|-$/i
131
+ # No more than one of the separator in a row.
132
+ if separator.length == 1
133
+ parameterized_string.squeeze!(separator)
134
134
  else
135
135
  re_sep = Regexp.escape(separator)
136
- re_duplicate_separator = /#{re_sep}{2,}/
137
- re_leading_trailing_separator = /^#{re_sep}|#{re_sep}$/i
136
+ parameterized_string.gsub!(/#{re_sep}{2,}/, separator)
138
137
  end
139
- # No more than one of the separator in a row.
140
- parameterized_string.gsub!(re_duplicate_separator, separator)
141
138
  # Remove leading/trailing separator.
142
- parameterized_string.gsub!(re_leading_trailing_separator, "")
139
+ parameterized_string.delete_prefix!(separator)
140
+ parameterized_string.delete_suffix!(separator)
143
141
  end
144
142
 
145
143
  parameterized_string.downcase! unless preserve_case
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "fiber"
4
-
5
3
  module ActiveSupport
6
4
  module IsolatedExecutionState # :nodoc:
7
5
  @isolation_level = nil
@@ -30,45 +28,47 @@ module ActiveSupport
30
28
  @isolation_level = level
31
29
  end
32
30
 
33
- def unique_id
34
- self[:__id__] ||= Object.new
35
- end
36
-
37
31
  def [](key)
38
- state[key]
32
+ if state = @scope.current.active_support_execution_state
33
+ state[key]
34
+ end
39
35
  end
40
36
 
41
37
  def []=(key, value)
38
+ state = (@scope.current.active_support_execution_state ||= {})
42
39
  state[key] = value
43
40
  end
44
41
 
45
42
  def key?(key)
46
- state.key?(key)
43
+ @scope.current.active_support_execution_state&.key?(key)
47
44
  end
48
45
 
49
46
  def delete(key)
50
- state.delete(key)
47
+ @scope.current.active_support_execution_state&.delete(key)
51
48
  end
52
49
 
53
50
  def clear
54
- state.clear
51
+ @scope.current.active_support_execution_state&.clear
55
52
  end
56
53
 
57
54
  def context
58
55
  scope.current
59
56
  end
60
57
 
61
- def share_with(other)
58
+ def share_with(other, except: [], &block)
62
59
  # Action Controller streaming spawns a new thread and copy thread locals.
63
60
  # We do the same here for backward compatibility, but this is very much a hack
64
61
  # and streaming should be rethought.
65
- context.active_support_execution_state = other.active_support_execution_state.dup
66
- end
67
-
68
- private
69
- def state
70
- context.active_support_execution_state ||= {}
62
+ if state = other.active_support_execution_state
63
+ copied_state = state.dup
64
+ Array(except).each { |key| copied_state.delete(key) }
71
65
  end
66
+
67
+ old_state, context.active_support_execution_state = context.active_support_execution_state, copied_state
68
+ block.call
69
+ ensure
70
+ context.active_support_execution_state = old_state
71
+ end
72
72
  end
73
73
 
74
74
  self.isolation_level = :thread