activesupport 7.1.6 → 8.1.1

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 (169) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +256 -1133
  3. data/README.rdoc +1 -1
  4. data/lib/active_support/array_inquirer.rb +1 -1
  5. data/lib/active_support/backtrace_cleaner.rb +81 -3
  6. data/lib/active_support/benchmark.rb +21 -0
  7. data/lib/active_support/benchmarkable.rb +3 -2
  8. data/lib/active_support/broadcast_logger.rb +65 -78
  9. data/lib/active_support/cache/file_store.rb +29 -14
  10. data/lib/active_support/cache/mem_cache_store.rb +42 -102
  11. data/lib/active_support/cache/memory_store.rb +11 -6
  12. data/lib/active_support/cache/null_store.rb +2 -2
  13. data/lib/active_support/cache/redis_cache_store.rb +58 -46
  14. data/lib/active_support/cache/serializer_with_fallback.rb +0 -23
  15. data/lib/active_support/cache/strategy/local_cache.rb +72 -27
  16. data/lib/active_support/cache/strategy/local_cache_middleware.rb +7 -7
  17. data/lib/active_support/cache.rb +146 -86
  18. data/lib/active_support/callbacks.rb +102 -126
  19. data/lib/active_support/class_attribute.rb +33 -0
  20. data/lib/active_support/code_generator.rb +9 -0
  21. data/lib/active_support/concurrency/load_interlock_aware_monitor.rb +8 -62
  22. data/lib/active_support/concurrency/share_lock.rb +0 -1
  23. data/lib/active_support/concurrency/thread_monitor.rb +55 -0
  24. data/lib/active_support/configurable.rb +34 -0
  25. data/lib/active_support/configuration_file.rb +15 -6
  26. data/lib/active_support/continuous_integration.rb +145 -0
  27. data/lib/active_support/core_ext/array/conversions.rb +3 -5
  28. data/lib/active_support/core_ext/array.rb +7 -7
  29. data/lib/active_support/core_ext/benchmark.rb +4 -14
  30. data/lib/active_support/core_ext/big_decimal.rb +1 -1
  31. data/lib/active_support/core_ext/class/attribute.rb +26 -19
  32. data/lib/active_support/core_ext/class/subclasses.rb +15 -35
  33. data/lib/active_support/core_ext/class.rb +2 -2
  34. data/lib/active_support/core_ext/date/blank.rb +4 -0
  35. data/lib/active_support/core_ext/date/conversions.rb +2 -2
  36. data/lib/active_support/core_ext/date.rb +5 -5
  37. data/lib/active_support/core_ext/date_and_time/compatibility.rb +1 -9
  38. data/lib/active_support/core_ext/date_time/blank.rb +4 -0
  39. data/lib/active_support/core_ext/date_time/compatibility.rb +3 -5
  40. data/lib/active_support/core_ext/date_time/conversions.rb +4 -6
  41. data/lib/active_support/core_ext/date_time.rb +5 -5
  42. data/lib/active_support/core_ext/digest/uuid.rb +6 -0
  43. data/lib/active_support/core_ext/digest.rb +1 -1
  44. data/lib/active_support/core_ext/enumerable.rb +25 -8
  45. data/lib/active_support/core_ext/erb/util.rb +10 -5
  46. data/lib/active_support/core_ext/file.rb +1 -1
  47. data/lib/active_support/core_ext/hash/deep_merge.rb +1 -0
  48. data/lib/active_support/core_ext/hash/except.rb +0 -12
  49. data/lib/active_support/core_ext/hash/keys.rb +4 -4
  50. data/lib/active_support/core_ext/hash.rb +8 -8
  51. data/lib/active_support/core_ext/integer.rb +3 -3
  52. data/lib/active_support/core_ext/kernel.rb +3 -3
  53. data/lib/active_support/core_ext/module/attr_internal.rb +16 -6
  54. data/lib/active_support/core_ext/module/delegation.rb +20 -163
  55. data/lib/active_support/core_ext/module/deprecation.rb +1 -4
  56. data/lib/active_support/core_ext/module/introspection.rb +3 -0
  57. data/lib/active_support/core_ext/module.rb +11 -11
  58. data/lib/active_support/core_ext/numeric/conversions.rb +3 -3
  59. data/lib/active_support/core_ext/numeric.rb +3 -3
  60. data/lib/active_support/core_ext/object/blank.rb +45 -1
  61. data/lib/active_support/core_ext/object/instance_variables.rb +11 -19
  62. data/lib/active_support/core_ext/object/json.rb +24 -11
  63. data/lib/active_support/core_ext/object/to_query.rb +7 -1
  64. data/lib/active_support/core_ext/object/try.rb +2 -2
  65. data/lib/active_support/core_ext/object/with.rb +5 -3
  66. data/lib/active_support/core_ext/object.rb +13 -13
  67. data/lib/active_support/core_ext/pathname/blank.rb +4 -0
  68. data/lib/active_support/core_ext/pathname.rb +2 -2
  69. data/lib/active_support/core_ext/range/overlap.rb +4 -4
  70. data/lib/active_support/core_ext/range/sole.rb +17 -0
  71. data/lib/active_support/core_ext/range.rb +4 -4
  72. data/lib/active_support/core_ext/securerandom.rb +4 -4
  73. data/lib/active_support/core_ext/string/conversions.rb +1 -1
  74. data/lib/active_support/core_ext/string/filters.rb +4 -4
  75. data/lib/active_support/core_ext/string/multibyte.rb +13 -4
  76. data/lib/active_support/core_ext/string/output_safety.rb +19 -19
  77. data/lib/active_support/core_ext/string.rb +13 -13
  78. data/lib/active_support/core_ext/symbol.rb +1 -1
  79. data/lib/active_support/core_ext/thread/backtrace/location.rb +2 -7
  80. data/lib/active_support/core_ext/time/calculations.rb +25 -30
  81. data/lib/active_support/core_ext/time/compatibility.rb +2 -3
  82. data/lib/active_support/core_ext/time/conversions.rb +2 -2
  83. data/lib/active_support/core_ext/time/zones.rb +1 -1
  84. data/lib/active_support/core_ext/time.rb +5 -5
  85. data/lib/active_support/core_ext.rb +1 -2
  86. data/lib/active_support/current_attributes/test_helper.rb +2 -2
  87. data/lib/active_support/current_attributes.rb +58 -50
  88. data/lib/active_support/delegation.rb +200 -0
  89. data/lib/active_support/dependencies/autoload.rb +0 -12
  90. data/lib/active_support/dependencies/interlock.rb +11 -5
  91. data/lib/active_support/dependencies.rb +6 -2
  92. data/lib/active_support/deprecation/constant_accessor.rb +47 -26
  93. data/lib/active_support/deprecation/proxy_wrappers.rb +9 -12
  94. data/lib/active_support/deprecation/reporting.rb +5 -17
  95. data/lib/active_support/deprecation.rb +8 -5
  96. data/lib/active_support/descendants_tracker.rb +9 -87
  97. data/lib/active_support/duration/iso8601_parser.rb +2 -2
  98. data/lib/active_support/duration/iso8601_serializer.rb +1 -2
  99. data/lib/active_support/duration.rb +25 -16
  100. data/lib/active_support/editor.rb +70 -0
  101. data/lib/active_support/encrypted_configuration.rb +20 -2
  102. data/lib/active_support/encrypted_file.rb +1 -1
  103. data/lib/active_support/error_reporter.rb +121 -6
  104. data/lib/active_support/event_reporter/test_helper.rb +32 -0
  105. data/lib/active_support/event_reporter.rb +592 -0
  106. data/lib/active_support/evented_file_update_checker.rb +5 -3
  107. data/lib/active_support/execution_context.rb +64 -7
  108. data/lib/active_support/execution_wrapper.rb +1 -2
  109. data/lib/active_support/file_update_checker.rb +9 -7
  110. data/lib/active_support/fork_tracker.rb +2 -38
  111. data/lib/active_support/gem_version.rb +2 -2
  112. data/lib/active_support/gzip.rb +1 -0
  113. data/lib/active_support/hash_with_indifferent_access.rb +66 -45
  114. data/lib/active_support/html_safe_translation.rb +3 -0
  115. data/lib/active_support/i18n_railtie.rb +19 -11
  116. data/lib/active_support/inflector/inflections.rb +31 -15
  117. data/lib/active_support/inflector/transliterate.rb +6 -8
  118. data/lib/active_support/isolated_execution_state.rb +12 -17
  119. data/lib/active_support/json/decoding.rb +6 -4
  120. data/lib/active_support/json/encoding.rb +157 -21
  121. data/lib/active_support/lazy_load_hooks.rb +1 -1
  122. data/lib/active_support/log_subscriber.rb +2 -18
  123. data/lib/active_support/logger.rb +15 -2
  124. data/lib/active_support/logger_thread_safe_level.rb +4 -9
  125. data/lib/active_support/message_encryptors.rb +54 -2
  126. data/lib/active_support/message_pack/extensions.rb +20 -2
  127. data/lib/active_support/message_verifier.rb +21 -0
  128. data/lib/active_support/message_verifiers.rb +57 -3
  129. data/lib/active_support/messages/rotation_coordinator.rb +9 -0
  130. data/lib/active_support/messages/rotator.rb +10 -0
  131. data/lib/active_support/multibyte/chars.rb +14 -4
  132. data/lib/active_support/multibyte.rb +4 -0
  133. data/lib/active_support/notifications/fanout.rb +68 -50
  134. data/lib/active_support/notifications/instrumenter.rb +22 -19
  135. data/lib/active_support/notifications.rb +28 -27
  136. data/lib/active_support/number_helper/number_converter.rb +2 -2
  137. data/lib/active_support/number_helper.rb +22 -0
  138. data/lib/active_support/option_merger.rb +2 -2
  139. data/lib/active_support/ordered_options.rb +53 -15
  140. data/lib/active_support/railtie.rb +36 -20
  141. data/lib/active_support/string_inquirer.rb +1 -1
  142. data/lib/active_support/structured_event_subscriber.rb +99 -0
  143. data/lib/active_support/subscriber.rb +1 -5
  144. data/lib/active_support/syntax_error_proxy.rb +3 -0
  145. data/lib/active_support/tagged_logging.rb +5 -1
  146. data/lib/active_support/test_case.rb +63 -6
  147. data/lib/active_support/testing/assertions.rb +113 -27
  148. data/lib/active_support/testing/constant_stubbing.rb +30 -8
  149. data/lib/active_support/testing/deprecation.rb +5 -12
  150. data/lib/active_support/testing/error_reporter_assertions.rb +18 -1
  151. data/lib/active_support/testing/event_reporter_assertions.rb +227 -0
  152. data/lib/active_support/testing/isolation.rb +19 -9
  153. data/lib/active_support/testing/method_call_assertions.rb +2 -16
  154. data/lib/active_support/testing/notification_assertions.rb +92 -0
  155. data/lib/active_support/testing/parallelization/server.rb +18 -2
  156. data/lib/active_support/testing/parallelization/worker.rb +4 -2
  157. data/lib/active_support/testing/parallelization.rb +25 -1
  158. data/lib/active_support/testing/tests_without_assertions.rb +19 -0
  159. data/lib/active_support/testing/time_helpers.rb +11 -6
  160. data/lib/active_support/time_with_zone.rb +39 -26
  161. data/lib/active_support/values/time_zone.rb +26 -17
  162. data/lib/active_support/xml_mini.rb +14 -4
  163. data/lib/active_support.rb +22 -9
  164. metadata +31 -17
  165. data/lib/active_support/core_ext/range/each.rb +0 -24
  166. data/lib/active_support/deprecation/instance_delegator.rb +0 -65
  167. data/lib/active_support/proxy_object.rb +0 -17
  168. data/lib/active_support/ruby_features.rb +0 -7
  169. data/lib/active_support/testing/strict_warnings.rb +0 -39
@@ -2,8 +2,41 @@
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
+ end
27
+
5
28
  @after_change_callbacks = []
29
+
30
+ # Execution context nesting should only legitimately happen during test
31
+ # because the test case itself is wrapped in an executor, and it might call
32
+ # into a controller or job which should be executed with their own fresh context.
33
+ # However in production this should never happen, and for extra safety we make sure to
34
+ # fully clear the state at the end of the request or job cycle.
35
+ @nestable = false
36
+
6
37
  class << self
38
+ attr_accessor :nestable
39
+
7
40
  def after_change(&block)
8
41
  @after_change_callbacks << block
9
42
  end
@@ -14,9 +47,11 @@ module ActiveSupport
14
47
  options.symbolize_keys!
15
48
  keys = options.keys
16
49
 
17
- store = self.store
50
+ store = record.store
18
51
 
19
- previous_context = keys.zip(store.values_at(*keys)).to_h
52
+ previous_context = if block_given?
53
+ keys.zip(store.values_at(*keys)).to_h
54
+ end
20
55
 
21
56
  store.merge!(options)
22
57
  @after_change_callbacks.each(&:call)
@@ -32,21 +67,43 @@ module ActiveSupport
32
67
  end
33
68
 
34
69
  def []=(key, value)
35
- store[key.to_sym] = value
70
+ record.store[key.to_sym] = value
36
71
  @after_change_callbacks.each(&:call)
37
72
  end
38
73
 
39
74
  def to_h
40
- store.dup
75
+ record.store.dup
76
+ end
77
+
78
+ def push
79
+ if @nestable
80
+ record.push
81
+ else
82
+ clear
83
+ end
84
+ self
85
+ end
86
+
87
+ def pop
88
+ if @nestable
89
+ record.pop
90
+ else
91
+ clear
92
+ end
93
+ self
41
94
  end
42
95
 
43
96
  def clear
44
- store.clear
97
+ IsolatedExecutionState[:active_support_execution_context] = nil
98
+ end
99
+
100
+ def current_attributes_instances
101
+ record.current_attributes_instances
45
102
  end
46
103
 
47
104
  private
48
- def store
49
- IsolatedExecutionState[:active_support_execution_context] ||= {}
105
+ def record
106
+ IsolatedExecutionState[:active_support_execution_context] ||= Record.new
50
107
  end
51
108
  end
52
109
  end
@@ -2,7 +2,6 @@
2
2
 
3
3
  require "active_support/error_reporter"
4
4
  require "active_support/callbacks"
5
- require "concurrent/hash"
6
5
 
7
6
  module ActiveSupport
8
7
  class ExecutionWrapper
@@ -90,7 +89,7 @@ module ActiveSupport
90
89
  instance = run!
91
90
  begin
92
91
  yield
93
- rescue => error
92
+ rescue Exception => error
94
93
  error_reporter&.report(error, handled: false, source: source)
95
94
  raise
96
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,8 +106,8 @@ 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
107
- all
109
+ all.concat(Dir[*@globs]) if @globs
110
+ all.tap(&:uniq!)
108
111
  end
109
112
  end
110
113
 
@@ -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)
@@ -2,7 +2,7 @@
2
2
 
3
3
  module ActiveSupport
4
4
  module ForkTracker # :nodoc:
5
- module ModernCoreExt
5
+ module CoreExt
6
6
  def _fork
7
7
  pid = super
8
8
  if pid == 0
@@ -12,27 +12,6 @@ module ActiveSupport
12
12
  end
13
13
  end
14
14
 
15
- module CoreExt
16
- def fork(...)
17
- if block_given?
18
- super do
19
- ForkTracker.check!
20
- yield
21
- end
22
- else
23
- unless pid = super
24
- ForkTracker.check!
25
- end
26
- pid
27
- end
28
- end
29
- end
30
-
31
- module CoreExtPrivate
32
- include CoreExt
33
- private :fork
34
- end
35
-
36
15
  @pid = Process.pid
37
16
  @callbacks = []
38
17
 
@@ -45,23 +24,8 @@ module ActiveSupport
45
24
  end
46
25
  end
47
26
 
48
- if Process.respond_to?(:_fork) # Ruby 3.1+
49
- def check!
50
- # We trust the `_fork` callback
51
- end
52
- else
53
- alias_method :check!, :after_fork_callback
54
- end
55
-
56
27
  def hook!
57
- if Process.respond_to?(:_fork) # Ruby 3.1+
58
- ::Process.singleton_class.prepend(ModernCoreExt)
59
- elsif Process.respond_to?(:fork)
60
- ::Object.prepend(CoreExtPrivate) if RUBY_VERSION < "3.0"
61
- ::Kernel.prepend(CoreExtPrivate)
62
- ::Kernel.singleton_class.prepend(CoreExt)
63
- ::Process.singleton_class.prepend(CoreExt)
64
- end
28
+ ::Process.singleton_class.prepend(CoreExt)
65
29
  end
66
30
 
67
31
  def after_fork(&block)
@@ -7,9 +7,9 @@ module ActiveSupport
7
7
  end
8
8
 
9
9
  module VERSION
10
- MAJOR = 7
10
+ MAJOR = 8
11
11
  MINOR = 1
12
- TINY = 6
12
+ TINY = 1
13
13
  PRE = nil
14
14
 
15
15
  STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
@@ -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,33 +393,27 @@ 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)
396
+ copy = Hash[self]
397
+ copy.transform_values! { |v| convert_value_to_hash(v) }
398
+ copy_defaults(copy)
399
+ end
383
400
 
384
- each do |key, value|
385
- _new_hash[key] = convert_value(value, conversion: :to_hash)
386
- end
387
- _new_hash
401
+ def to_proc
402
+ proc { |key| self[key] }
388
403
  end
389
404
 
390
405
  private
391
- if Symbol.method_defined?(:name)
392
- def convert_key(key)
393
- key.kind_of?(Symbol) ? key.name : key
394
- end
395
- else
396
- def convert_key(key)
397
- key.kind_of?(Symbol) ? key.to_s : key
398
- end
406
+ def cast(other)
407
+ self.class === other ? other : self.class.new(other)
408
+ end
409
+
410
+ def convert_key(key)
411
+ Symbol === key ? key.name : key
399
412
  end
400
413
 
401
414
  def convert_value(value, conversion: nil)
402
415
  if value.is_a? Hash
403
- if conversion == :to_hash
404
- value.to_hash
405
- else
406
- value.nested_under_indifferent_access
407
- end
416
+ value.nested_under_indifferent_access
408
417
  elsif value.is_a?(Array)
409
418
  if conversion != :assignment || value.frozen?
410
419
  value = value.dup
@@ -415,12 +424,24 @@ module ActiveSupport
415
424
  end
416
425
  end
417
426
 
418
- 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)
419
439
  if default_proc
420
440
  target.default_proc = default_proc.dup
421
441
  else
422
442
  target.default = default
423
443
  end
444
+ target
424
445
  end
425
446
 
426
447
  def update_with_single_argument(other_hash, block)
@@ -9,11 +9,14 @@ module ActiveSupport
9
9
  html_safe_options = html_escape_translation_options(options)
10
10
 
11
11
  exception = false
12
+
12
13
  exception_handler = ->(*args) do
13
14
  exception = true
14
15
  I18n.exception_handler.call(*args)
15
16
  end
17
+
16
18
  translation = I18n.translate(key, **html_safe_options, exception_handler: exception_handler)
19
+
17
20
  if exception
18
21
  translation
19
22
  else
@@ -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,44 +30,59 @@ 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|
71
87
  return @__instance__[k] if @__instance__.key?(k)
72
88
  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