activesupport 7.2.3.1 → 8.1.4.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 (135) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +584 -234
  3. data/lib/active_support/backtrace_cleaner.rb +72 -1
  4. data/lib/active_support/benchmark.rb +21 -0
  5. data/lib/active_support/benchmarkable.rb +3 -2
  6. data/lib/active_support/cache/file_store.rb +13 -2
  7. data/lib/active_support/cache/mem_cache_store.rb +17 -14
  8. data/lib/active_support/cache/memory_store.rb +9 -3
  9. data/lib/active_support/cache/redis_cache_store.rb +51 -34
  10. data/lib/active_support/cache/strategy/local_cache.rb +17 -8
  11. data/lib/active_support/cache/strategy/local_cache_middleware.rb +7 -7
  12. data/lib/active_support/cache.rb +86 -18
  13. data/lib/active_support/callbacks.rb +25 -11
  14. data/lib/active_support/class_attribute.rb +33 -0
  15. data/lib/active_support/code_generator.rb +9 -0
  16. data/lib/active_support/concurrency/load_interlock_aware_monitor.rb +8 -62
  17. data/lib/active_support/concurrency/share_lock.rb +0 -1
  18. data/lib/active_support/concurrency/thread_monitor.rb +55 -0
  19. data/lib/active_support/configurable.rb +34 -0
  20. data/lib/active_support/configuration_file.rb +15 -6
  21. data/lib/active_support/continuous_integration.rb +145 -0
  22. data/lib/active_support/core_ext/array/conversions.rb +3 -3
  23. data/lib/active_support/core_ext/array.rb +7 -7
  24. data/lib/active_support/core_ext/benchmark.rb +0 -16
  25. data/lib/active_support/core_ext/big_decimal.rb +1 -1
  26. data/lib/active_support/core_ext/class/attribute.rb +24 -18
  27. data/lib/active_support/core_ext/class.rb +2 -2
  28. data/lib/active_support/core_ext/date/conversions.rb +2 -0
  29. data/lib/active_support/core_ext/date.rb +5 -5
  30. data/lib/active_support/core_ext/date_and_time/compatibility.rb +0 -35
  31. data/lib/active_support/core_ext/date_time/calculations.rb +1 -0
  32. data/lib/active_support/core_ext/date_time/compatibility.rb +3 -5
  33. data/lib/active_support/core_ext/date_time.rb +5 -5
  34. data/lib/active_support/core_ext/digest.rb +1 -1
  35. data/lib/active_support/core_ext/enumerable.rb +8 -3
  36. data/lib/active_support/core_ext/erb/util.rb +3 -3
  37. data/lib/active_support/core_ext/file.rb +1 -1
  38. data/lib/active_support/core_ext/hash/deep_merge.rb +1 -0
  39. data/lib/active_support/core_ext/hash/except.rb +0 -12
  40. data/lib/active_support/core_ext/hash.rb +8 -8
  41. data/lib/active_support/core_ext/integer.rb +3 -3
  42. data/lib/active_support/core_ext/kernel.rb +3 -3
  43. data/lib/active_support/core_ext/module/attr_internal.rb +3 -4
  44. data/lib/active_support/core_ext/module.rb +11 -11
  45. data/lib/active_support/core_ext/numeric.rb +3 -3
  46. data/lib/active_support/core_ext/object/instance_variables.rb +1 -1
  47. data/lib/active_support/core_ext/object/json.rb +24 -11
  48. data/lib/active_support/core_ext/object/to_query.rb +7 -1
  49. data/lib/active_support/core_ext/object.rb +13 -13
  50. data/lib/active_support/core_ext/pathname.rb +2 -2
  51. data/lib/active_support/core_ext/range/compare_range.rb +2 -14
  52. data/lib/active_support/core_ext/range/overlap.rb +3 -3
  53. data/lib/active_support/core_ext/range/sole.rb +2 -0
  54. data/lib/active_support/core_ext/range.rb +4 -5
  55. data/lib/active_support/core_ext/string/filters.rb +2 -0
  56. data/lib/active_support/core_ext/string/inflections.rb +1 -1
  57. data/lib/active_support/core_ext/string/multibyte.rb +10 -1
  58. data/lib/active_support/core_ext/string/output_safety.rb +27 -13
  59. data/lib/active_support/core_ext/string.rb +13 -13
  60. data/lib/active_support/core_ext/symbol.rb +1 -1
  61. data/lib/active_support/core_ext/thread/backtrace/location.rb +2 -7
  62. data/lib/active_support/core_ext/time/calculations.rb +11 -2
  63. data/lib/active_support/core_ext/time/compatibility.rb +2 -27
  64. data/lib/active_support/core_ext/time/conversions.rb +2 -0
  65. data/lib/active_support/core_ext/time.rb +5 -5
  66. data/lib/active_support/current_attributes/test_helper.rb +2 -2
  67. data/lib/active_support/current_attributes.rb +13 -10
  68. data/lib/active_support/delegation.rb +25 -44
  69. data/lib/active_support/dependencies/interlock.rb +11 -5
  70. data/lib/active_support/dependencies.rb +6 -2
  71. data/lib/active_support/deprecation/reporting.rb +4 -21
  72. data/lib/active_support/deprecation.rb +1 -1
  73. data/lib/active_support/duration.rb +20 -16
  74. data/lib/active_support/editor.rb +70 -0
  75. data/lib/active_support/encrypted_configuration.rb +20 -2
  76. data/lib/active_support/error_reporter.rb +76 -2
  77. data/lib/active_support/event_reporter/test_helper.rb +32 -0
  78. data/lib/active_support/event_reporter.rb +592 -0
  79. data/lib/active_support/evented_file_update_checker.rb +5 -2
  80. data/lib/active_support/execution_context.rb +75 -7
  81. data/lib/active_support/file_update_checker.rb +7 -5
  82. data/lib/active_support/gem_version.rb +3 -3
  83. data/lib/active_support/gzip.rb +1 -0
  84. data/lib/active_support/hash_with_indifferent_access.rb +44 -24
  85. data/lib/active_support/i18n_railtie.rb +19 -11
  86. data/lib/active_support/inflector/inflections.rb +34 -16
  87. data/lib/active_support/inflector/methods.rb +6 -22
  88. data/lib/active_support/inflector/transliterate.rb +18 -10
  89. data/lib/active_support/isolated_execution_state.rb +17 -17
  90. data/lib/active_support/json/decoding.rb +9 -3
  91. data/lib/active_support/json/encoding.rb +149 -19
  92. data/lib/active_support/log_subscriber.rb +2 -6
  93. data/lib/active_support/logger_thread_safe_level.rb +6 -3
  94. data/lib/active_support/message_encryptors.rb +52 -0
  95. data/lib/active_support/message_pack/extensions.rb +23 -1
  96. data/lib/active_support/message_verifiers.rb +52 -0
  97. data/lib/active_support/messages/rotation_coordinator.rb +9 -0
  98. data/lib/active_support/messages/rotator.rb +5 -0
  99. data/lib/active_support/messages/serializer_with_fallback.rb +1 -1
  100. data/lib/active_support/multibyte/chars.rb +8 -1
  101. data/lib/active_support/multibyte.rb +4 -0
  102. data/lib/active_support/notifications/fanout.rb +64 -43
  103. data/lib/active_support/notifications/instrumenter.rb +1 -1
  104. data/lib/active_support/notifications.rb +2 -2
  105. data/lib/active_support/number_helper/number_converter.rb +4 -1
  106. data/lib/active_support/number_helper/number_to_currency_converter.rb +1 -1
  107. data/lib/active_support/number_helper/number_to_delimited_converter.rb +3 -4
  108. data/lib/active_support/number_helper/number_to_phone_converter.rb +3 -7
  109. data/lib/active_support/number_helper/rounding_helper.rb +1 -1
  110. data/lib/active_support/number_helper.rb +22 -0
  111. data/lib/active_support/ordered_options.rb +2 -2
  112. data/lib/active_support/railtie.rb +32 -9
  113. data/lib/active_support/security_utils.rb +4 -5
  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 +9 -1
  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/worker.rb +7 -1
  126. data/lib/active_support/testing/parallelization.rb +17 -0
  127. data/lib/active_support/testing/tests_without_assertions.rb +1 -1
  128. data/lib/active_support/testing/time_helpers.rb +9 -4
  129. data/lib/active_support/time_with_zone.rb +36 -23
  130. data/lib/active_support/values/time_zone.rb +22 -13
  131. data/lib/active_support/xml_mini.rb +1 -2
  132. data/lib/active_support.rb +21 -9
  133. metadata +33 -17
  134. data/lib/active_support/core_ext/range/each.rb +0 -24
  135. data/lib/active_support/proxy_object.rb +0 -20
@@ -2,8 +2,48 @@
2
2
 
3
3
  module ActiveSupport
4
4
  module ExecutionContext # :nodoc:
5
+ class Record # :nodoc:
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
@@ -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
@@ -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,9 +7,9 @@ module ActiveSupport
7
7
  end
8
8
 
9
9
  module VERSION
10
- MAJOR = 7
11
- MINOR = 2
12
- TINY = 3
10
+ MAJOR = 8
11
+ MINOR = 1
12
+ TINY = 4
13
13
  PRE = "1"
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
- self.default = hash.default if hash.default
78
+ self.default = hash.default unless hash.default.nil?
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
  #
@@ -279,13 +295,13 @@ module ActiveSupport
279
295
  # hash['a'] = nil
280
296
  # hash.reverse_merge(a: 0, b: 1) # => {"a"=>nil, "b"=>1}
281
297
  def reverse_merge(other_hash)
282
- super(self.class.new(other_hash))
298
+ super(cast(other_hash))
283
299
  end
284
300
  alias_method :with_defaults, :reverse_merge
285
301
 
286
302
  # Same semantics as +reverse_merge+ but modifies the receiver in-place.
287
303
  def reverse_merge!(other_hash)
288
- super(self.class.new(other_hash))
304
+ super(cast(other_hash))
289
305
  end
290
306
  alias_method :with_defaults!, :reverse_merge!
291
307
 
@@ -294,7 +310,7 @@ module ActiveSupport
294
310
  # h = { "a" => 100, "b" => 200 }
295
311
  # h.replace({ "c" => 300, "d" => 400 }) # => {"c"=>300, "d"=>400}
296
312
  def replace(other_hash)
297
- super(self.class.new(other_hash))
313
+ super(cast(other_hash))
298
314
  end
299
315
 
300
316
  # Removes the specified key from the hash.
@@ -311,10 +327,6 @@ module ActiveSupport
311
327
  end
312
328
  alias_method :without, :except
313
329
 
314
- def stringify_keys!; self end
315
- def deep_stringify_keys!; self end
316
- def stringify_keys; dup end
317
- def deep_stringify_keys; dup end
318
330
  undef :symbolize_keys!
319
331
  undef :deep_symbolize_keys!
320
332
  def symbolize_keys; to_hash.symbolize_keys! end
@@ -326,6 +338,7 @@ module ActiveSupport
326
338
  return to_enum(:select) unless block_given?
327
339
  dup.tap { |hash| hash.select!(*args, &block) }
328
340
  end
341
+ alias_method :filter, :select
329
342
 
330
343
  def reject(*args, &block)
331
344
  return to_enum(:reject) unless block_given?
@@ -381,13 +394,9 @@ module ActiveSupport
381
394
 
382
395
  # Convert to a regular hash with string keys.
383
396
  def to_hash
384
- _new_hash = Hash.new
385
- copy_defaults(_new_hash)
386
-
387
- each do |key, value|
388
- _new_hash[key] = convert_value(value, conversion: :to_hash)
389
- end
390
- _new_hash
397
+ copy = Hash[self]
398
+ copy.transform_values! { |v| convert_value_to_hash(v) }
399
+ copy_defaults(copy)
391
400
  end
392
401
 
393
402
  def to_proc
@@ -395,17 +404,17 @@ module ActiveSupport
395
404
  end
396
405
 
397
406
  private
407
+ def cast(other)
408
+ self.class === other ? other : self.class.new(other)
409
+ end
410
+
398
411
  def convert_key(key)
399
412
  Symbol === key ? key.name : key
400
413
  end
401
414
 
402
415
  def convert_value(value, conversion: nil)
403
416
  if value.is_a? Hash
404
- if conversion == :to_hash
405
- value.to_hash
406
- else
407
- value.nested_under_indifferent_access
408
- end
417
+ value.nested_under_indifferent_access
409
418
  elsif value.is_a?(Array)
410
419
  if conversion != :assignment || value.frozen?
411
420
  value = value.dup
@@ -416,6 +425,17 @@ module ActiveSupport
416
425
  end
417
426
  end
418
427
 
428
+ def convert_value_to_hash(value)
429
+ if value.is_a? Hash
430
+ value.to_hash
431
+ elsif value.is_a?(Array)
432
+ value.map { |e| convert_value_to_hash(e) }
433
+ else
434
+ value
435
+ end
436
+ end
437
+
438
+
419
439
  def copy_defaults(target)
420
440
  if default_proc
421
441
  target.default_proc = default_proc.dup
@@ -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
@@ -190,7 +190,7 @@ module ActiveSupport
190
190
  # titleize('raiders_of_the_lost_ark') # => "Raiders Of The Lost Ark"
191
191
  # titleize('string_ending_with_id', keep_id_suffix: true) # => "String Ending With Id"
192
192
  def titleize(word, keep_id_suffix: false)
193
- humanize(underscore(word), keep_id_suffix: keep_id_suffix).gsub(/\b(?<!\w['’`()])[a-z]/) do |match|
193
+ humanize(underscore(word), keep_id_suffix: keep_id_suffix).gsub(/\b(?<!\w['’`()])\p{Lower}/) do |match|
194
194
  match.capitalize
195
195
  end
196
196
  end
@@ -319,7 +319,8 @@ module ActiveSupport
319
319
  e.name.to_s == camel_cased_word.to_s)
320
320
  rescue LoadError => e
321
321
  message = e.respond_to?(:original_message) ? e.original_message : e.message
322
- raise unless /Unable to autoload constant #{const_regexp(camel_cased_word)}/.match?(message)
322
+ constant_name = camel_cased_word.split("::").find { |part| !part.empty? } || camel_cased_word
323
+ raise unless message.include?("Unable to autoload constant #{constant_name}")
323
324
  end
324
325
 
325
326
  # Returns the suffix that should be added to a number to denote the position
@@ -349,23 +350,6 @@ module ActiveSupport
349
350
  end
350
351
 
351
352
  private
352
- # Mounts a regular expression, returned as a string to ease interpolation,
353
- # that will match part by part the given constant.
354
- #
355
- # const_regexp("Foo::Bar::Baz") # => "Foo(::Bar(::Baz)?)?"
356
- # const_regexp("::") # => "::"
357
- def const_regexp(camel_cased_word)
358
- parts = camel_cased_word.split("::")
359
-
360
- return Regexp.escape(camel_cased_word) if parts.empty?
361
-
362
- last = parts.pop
363
-
364
- parts.reverse!.inject(last) do |acc, part|
365
- part.empty? ? acc : "#{part}(::#{acc})?"
366
- end
367
- end
368
-
369
353
  # Applies inflection rules for +singularize+ and +pluralize+.
370
354
  #
371
355
  # If passed an optional +locale+ parameter, the uncountables will be
@@ -66,7 +66,10 @@ module ActiveSupport
66
66
  raise ArgumentError, "Cannot transliterate strings with #{string.encoding} encoding" unless ALLOWED_ENCODINGS_FOR_TRANSLITERATE.include?(string.encoding)
67
67
 
68
68
  return string.dup if string.ascii_only?
69
- string = string.dup if string.frozen?
69
+
70
+ # Operate on a copy so the in-place force_encoding/encode! below never
71
+ # mutate the caller's (possibly non-frozen) string.
72
+ string = string.dup
70
73
 
71
74
  input_encoding = string.encoding
72
75
 
@@ -105,6 +108,11 @@ module ActiveSupport
105
108
  # parameterize("Donald E. Knuth", separator: '_') # => "donald_e_knuth"
106
109
  # parameterize("^très|Jolie__ ", separator: '_') # => "tres_jolie"
107
110
  #
111
+ # To remove the unwanted characters instead of replacing them, pass an
112
+ # empty separator. +nil+ is treated the same way.
113
+ #
114
+ # parameterize("Donald E. Knuth", separator: '') # => "donaldeknuth"
115
+ #
108
116
  # To preserve the case of the characters in a string, use the +preserve_case+ argument.
109
117
  #
110
118
  # parameterize("Donald E. Knuth", preserve_case: true) # => "Donald-E-Knuth"
@@ -121,25 +129,25 @@ module ActiveSupport
121
129
  # By default, this parameter is set to <tt>nil</tt> and it will use
122
130
  # the configured <tt>I18n.locale</tt>.
123
131
  def parameterize(string, separator: "-", preserve_case: false, locale: nil)
132
+ separator ||= ""
133
+
124
134
  # Replace accented chars with their ASCII equivalents.
125
135
  parameterized_string = transliterate(string, locale: locale)
126
136
 
127
137
  # Turn unwanted chars into the separator.
128
138
  parameterized_string.gsub!(/[^a-z0-9\-_]+/i, separator)
129
139
 
130
- unless separator.nil? || separator.empty?
131
- if separator == "-"
132
- re_duplicate_separator = /-{2,}/
133
- re_leading_trailing_separator = /^-|-$/i
140
+ unless separator.empty?
141
+ # No more than one of the separator in a row.
142
+ if separator.length == 1
143
+ parameterized_string.squeeze!(separator)
134
144
  else
135
145
  re_sep = Regexp.escape(separator)
136
- re_duplicate_separator = /#{re_sep}{2,}/
137
- re_leading_trailing_separator = /^#{re_sep}|#{re_sep}$/i
146
+ parameterized_string.gsub!(/#{re_sep}{2,}/, separator)
138
147
  end
139
- # No more than one of the separator in a row.
140
- parameterized_string.gsub!(re_duplicate_separator, separator)
141
148
  # Remove leading/trailing separator.
142
- parameterized_string.gsub!(re_leading_trailing_separator, "")
149
+ parameterized_string.delete_prefix!(separator)
150
+ parameterized_string.delete_suffix!(separator)
143
151
  end
144
152
 
145
153
  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