activesupport 6.1.4.1 → 7.0.8.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (185) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +325 -395
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +2 -2
  5. data/lib/active_support/actionable_error.rb +1 -1
  6. data/lib/active_support/array_inquirer.rb +0 -2
  7. data/lib/active_support/backtrace_cleaner.rb +2 -2
  8. data/lib/active_support/benchmarkable.rb +2 -2
  9. data/lib/active_support/cache/file_store.rb +15 -9
  10. data/lib/active_support/cache/mem_cache_store.rb +148 -37
  11. data/lib/active_support/cache/memory_store.rb +24 -16
  12. data/lib/active_support/cache/null_store.rb +10 -2
  13. data/lib/active_support/cache/redis_cache_store.rb +68 -85
  14. data/lib/active_support/cache/strategy/local_cache.rb +38 -61
  15. data/lib/active_support/cache.rb +299 -147
  16. data/lib/active_support/callbacks.rb +184 -85
  17. data/lib/active_support/code_generator.rb +65 -0
  18. data/lib/active_support/concern.rb +5 -5
  19. data/lib/active_support/concurrency/load_interlock_aware_monitor.rb +2 -4
  20. data/lib/active_support/concurrency/share_lock.rb +2 -2
  21. data/lib/active_support/configurable.rb +8 -5
  22. data/lib/active_support/configuration_file.rb +1 -1
  23. data/lib/active_support/core_ext/array/access.rb +1 -5
  24. data/lib/active_support/core_ext/array/conversions.rb +13 -12
  25. data/lib/active_support/core_ext/array/deprecated_conversions.rb +25 -0
  26. data/lib/active_support/core_ext/array/grouping.rb +6 -6
  27. data/lib/active_support/core_ext/array/inquiry.rb +2 -2
  28. data/lib/active_support/core_ext/array.rb +1 -0
  29. data/lib/active_support/core_ext/big_decimal/conversions.rb +1 -1
  30. data/lib/active_support/core_ext/class/subclasses.rb +25 -17
  31. data/lib/active_support/core_ext/date/blank.rb +1 -1
  32. data/lib/active_support/core_ext/date/calculations.rb +24 -9
  33. data/lib/active_support/core_ext/date/conversions.rb +14 -14
  34. data/lib/active_support/core_ext/date/deprecated_conversions.rb +40 -0
  35. data/lib/active_support/core_ext/date.rb +1 -0
  36. data/lib/active_support/core_ext/date_and_time/calculations.rb +4 -4
  37. data/lib/active_support/core_ext/date_and_time/compatibility.rb +1 -1
  38. data/lib/active_support/core_ext/date_time/blank.rb +1 -1
  39. data/lib/active_support/core_ext/date_time/calculations.rb +4 -0
  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 +36 -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 +112 -38
  45. data/lib/active_support/core_ext/file/atomic.rb +3 -1
  46. data/lib/active_support/core_ext/hash/conversions.rb +0 -1
  47. data/lib/active_support/core_ext/hash/deep_transform_values.rb +3 -3
  48. data/lib/active_support/core_ext/hash/indifferent_access.rb +3 -3
  49. data/lib/active_support/core_ext/hash/keys.rb +4 -4
  50. data/lib/active_support/core_ext/integer/inflections.rb +12 -12
  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/module/attribute_accessors.rb +2 -0
  54. data/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb +19 -10
  55. data/lib/active_support/core_ext/module/delegation.rb +2 -8
  56. data/lib/active_support/core_ext/name_error.rb +2 -8
  57. data/lib/active_support/core_ext/numeric/conversions.rb +80 -77
  58. data/lib/active_support/core_ext/numeric/deprecated_conversions.rb +60 -0
  59. data/lib/active_support/core_ext/numeric.rb +1 -0
  60. data/lib/active_support/core_ext/object/acts_like.rb +29 -5
  61. data/lib/active_support/core_ext/object/blank.rb +2 -2
  62. data/lib/active_support/core_ext/object/deep_dup.rb +1 -1
  63. data/lib/active_support/core_ext/object/duplicable.rb +15 -4
  64. data/lib/active_support/core_ext/object/json.rb +30 -25
  65. data/lib/active_support/core_ext/object/to_query.rb +2 -4
  66. data/lib/active_support/core_ext/object/try.rb +20 -20
  67. data/lib/active_support/core_ext/object/with_options.rb +21 -2
  68. data/lib/active_support/core_ext/pathname/existence.rb +21 -0
  69. data/lib/active_support/core_ext/pathname.rb +3 -0
  70. data/lib/active_support/core_ext/range/compare_range.rb +0 -25
  71. data/lib/active_support/core_ext/range/conversions.rb +8 -8
  72. data/lib/active_support/core_ext/range/deprecated_conversions.rb +36 -0
  73. data/lib/active_support/core_ext/range/each.rb +1 -1
  74. data/lib/active_support/core_ext/range/include_time_with_zone.rb +3 -26
  75. data/lib/active_support/core_ext/range/overlaps.rb +1 -1
  76. data/lib/active_support/core_ext/range.rb +1 -1
  77. data/lib/active_support/core_ext/securerandom.rb +1 -1
  78. data/lib/active_support/core_ext/string/conversions.rb +2 -2
  79. data/lib/active_support/core_ext/string/filters.rb +1 -1
  80. data/lib/active_support/core_ext/string/inflections.rb +1 -5
  81. data/lib/active_support/core_ext/string/inquiry.rb +1 -1
  82. data/lib/active_support/core_ext/string/output_safety.rb +94 -38
  83. data/lib/active_support/core_ext/symbol/starts_ends_with.rb +0 -8
  84. data/lib/active_support/core_ext/time/calculations.rb +13 -8
  85. data/lib/active_support/core_ext/time/conversions.rb +13 -12
  86. data/lib/active_support/core_ext/time/deprecated_conversions.rb +73 -0
  87. data/lib/active_support/core_ext/time/zones.rb +10 -26
  88. data/lib/active_support/core_ext/time.rb +1 -0
  89. data/lib/active_support/core_ext/uri.rb +3 -27
  90. data/lib/active_support/core_ext.rb +1 -0
  91. data/lib/active_support/current_attributes.rb +31 -14
  92. data/lib/active_support/dependencies/interlock.rb +10 -18
  93. data/lib/active_support/dependencies/require_dependency.rb +28 -0
  94. data/lib/active_support/dependencies.rb +58 -788
  95. data/lib/active_support/deprecation/behaviors.rb +8 -5
  96. data/lib/active_support/deprecation/disallowed.rb +3 -3
  97. data/lib/active_support/deprecation/method_wrappers.rb +3 -3
  98. data/lib/active_support/deprecation/proxy_wrappers.rb +2 -2
  99. data/lib/active_support/deprecation.rb +2 -2
  100. data/lib/active_support/descendants_tracker.rb +174 -68
  101. data/lib/active_support/digest.rb +5 -3
  102. data/lib/active_support/duration/iso8601_parser.rb +3 -3
  103. data/lib/active_support/duration/iso8601_serializer.rb +9 -1
  104. data/lib/active_support/duration.rb +81 -51
  105. data/lib/active_support/encrypted_configuration.rb +45 -3
  106. data/lib/active_support/encrypted_file.rb +21 -10
  107. data/lib/active_support/environment_inquirer.rb +1 -1
  108. data/lib/active_support/error_reporter.rb +117 -0
  109. data/lib/active_support/evented_file_update_checker.rb +20 -7
  110. data/lib/active_support/execution_context/test_helper.rb +13 -0
  111. data/lib/active_support/execution_context.rb +53 -0
  112. data/lib/active_support/execution_wrapper.rb +43 -21
  113. data/lib/active_support/executor/test_helper.rb +7 -0
  114. data/lib/active_support/fork_tracker.rb +19 -12
  115. data/lib/active_support/gem_version.rb +5 -5
  116. data/lib/active_support/hash_with_indifferent_access.rb +3 -1
  117. data/lib/active_support/html_safe_translation.rb +43 -0
  118. data/lib/active_support/i18n.rb +1 -0
  119. data/lib/active_support/i18n_railtie.rb +1 -1
  120. data/lib/active_support/inflector/inflections.rb +23 -7
  121. data/lib/active_support/inflector/methods.rb +29 -55
  122. data/lib/active_support/inflector/transliterate.rb +1 -1
  123. data/lib/active_support/isolated_execution_state.rb +72 -0
  124. data/lib/active_support/json/encoding.rb +3 -3
  125. data/lib/active_support/key_generator.rb +22 -5
  126. data/lib/active_support/lazy_load_hooks.rb +28 -4
  127. data/lib/active_support/locale/en.yml +1 -1
  128. data/lib/active_support/log_subscriber/test_helper.rb +2 -2
  129. data/lib/active_support/log_subscriber.rb +15 -5
  130. data/lib/active_support/logger_thread_safe_level.rb +4 -13
  131. data/lib/active_support/message_encryptor.rb +12 -6
  132. data/lib/active_support/message_verifier.rb +46 -14
  133. data/lib/active_support/messages/metadata.rb +2 -2
  134. data/lib/active_support/multibyte/chars.rb +10 -11
  135. data/lib/active_support/multibyte/unicode.rb +0 -12
  136. data/lib/active_support/multibyte.rb +1 -1
  137. data/lib/active_support/notifications/fanout.rb +91 -65
  138. data/lib/active_support/notifications/instrumenter.rb +32 -15
  139. data/lib/active_support/notifications.rb +23 -23
  140. data/lib/active_support/number_helper/number_converter.rb +1 -3
  141. data/lib/active_support/number_helper/number_to_currency_converter.rb +11 -6
  142. data/lib/active_support/number_helper/number_to_delimited_converter.rb +1 -1
  143. data/lib/active_support/number_helper/number_to_human_size_converter.rb +1 -1
  144. data/lib/active_support/number_helper/number_to_phone_converter.rb +1 -1
  145. data/lib/active_support/number_helper/rounding_helper.rb +1 -5
  146. data/lib/active_support/number_helper.rb +4 -5
  147. data/lib/active_support/option_merger.rb +10 -18
  148. data/lib/active_support/ordered_hash.rb +1 -1
  149. data/lib/active_support/ordered_options.rb +1 -1
  150. data/lib/active_support/parameter_filter.rb +20 -11
  151. data/lib/active_support/per_thread_registry.rb +5 -0
  152. data/lib/active_support/railtie.rb +69 -19
  153. data/lib/active_support/reloader.rb +1 -1
  154. data/lib/active_support/rescuable.rb +12 -12
  155. data/lib/active_support/ruby_features.rb +7 -0
  156. data/lib/active_support/secure_compare_rotator.rb +2 -2
  157. data/lib/active_support/string_inquirer.rb +0 -2
  158. data/lib/active_support/subscriber.rb +7 -18
  159. data/lib/active_support/tagged_logging.rb +2 -2
  160. data/lib/active_support/test_case.rb +13 -21
  161. data/lib/active_support/testing/assertions.rb +36 -6
  162. data/lib/active_support/testing/deprecation.rb +52 -1
  163. data/lib/active_support/testing/isolation.rb +30 -29
  164. data/lib/active_support/testing/method_call_assertions.rb +5 -5
  165. data/lib/active_support/testing/parallelization/server.rb +4 -0
  166. data/lib/active_support/testing/parallelization/worker.rb +3 -0
  167. data/lib/active_support/testing/parallelization.rb +4 -0
  168. data/lib/active_support/testing/parallelize_executor.rb +76 -0
  169. data/lib/active_support/testing/stream.rb +3 -5
  170. data/lib/active_support/testing/tagged_logging.rb +1 -1
  171. data/lib/active_support/testing/time_helpers.rb +13 -2
  172. data/lib/active_support/time_with_zone.rb +43 -22
  173. data/lib/active_support/values/time_zone.rb +35 -14
  174. data/lib/active_support/version.rb +1 -1
  175. data/lib/active_support/xml_mini/jdom.rb +1 -1
  176. data/lib/active_support/xml_mini/libxml.rb +5 -5
  177. data/lib/active_support/xml_mini/libxmlsax.rb +1 -1
  178. data/lib/active_support/xml_mini/nokogiri.rb +4 -4
  179. data/lib/active_support/xml_mini/nokogirisax.rb +1 -1
  180. data/lib/active_support/xml_mini/rexml.rb +1 -1
  181. data/lib/active_support/xml_mini.rb +5 -4
  182. data/lib/active_support.rb +17 -1
  183. metadata +26 -23
  184. data/lib/active_support/core_ext/marshal.rb +0 -26
  185. data/lib/active_support/dependencies/zeitwerk_integration.rb +0 -117
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveSupport
4
+ module ExecutionContext # :nodoc:
5
+ @after_change_callbacks = []
6
+ class << self
7
+ def after_change(&block)
8
+ @after_change_callbacks << block
9
+ end
10
+
11
+ # Updates the execution context. If a block is given, it resets the provided keys to their
12
+ # previous value once the block exits.
13
+ def set(**options)
14
+ options.symbolize_keys!
15
+ keys = options.keys
16
+
17
+ store = self.store
18
+
19
+ previous_context = keys.zip(store.values_at(*keys)).to_h
20
+
21
+ store.merge!(options)
22
+ @after_change_callbacks.each(&:call)
23
+
24
+ if block_given?
25
+ begin
26
+ yield
27
+ ensure
28
+ store.merge!(previous_context)
29
+ @after_change_callbacks.each(&:call)
30
+ end
31
+ end
32
+ end
33
+
34
+ def []=(key, value)
35
+ store[key.to_sym] = value
36
+ @after_change_callbacks.each(&:call)
37
+ end
38
+
39
+ def to_h
40
+ store.dup
41
+ end
42
+
43
+ def clear
44
+ store.clear
45
+ end
46
+
47
+ private
48
+ def store
49
+ IsolatedExecutionState[:active_support_execution_context] ||= {}
50
+ end
51
+ end
52
+ end
53
+ end
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "active_support/error_reporter"
3
4
  require "active_support/callbacks"
4
5
  require "concurrent/hash"
5
6
 
@@ -63,18 +64,21 @@ module ActiveSupport
63
64
  # after the work has been performed.
64
65
  #
65
66
  # Where possible, prefer +wrap+.
66
- def self.run!
67
- if active?
68
- Null
67
+ def self.run!(reset: false)
68
+ if reset
69
+ lost_instance = IsolatedExecutionState.delete(active_key)
70
+ lost_instance&.complete!
69
71
  else
70
- new.tap do |instance|
71
- success = nil
72
- begin
73
- instance.run!
74
- success = true
75
- ensure
76
- instance.complete! unless success
77
- end
72
+ return Null if active?
73
+ end
74
+
75
+ new.tap do |instance|
76
+ success = nil
77
+ begin
78
+ instance.run!
79
+ success = true
80
+ ensure
81
+ instance.complete! unless success
78
82
  end
79
83
  end
80
84
  end
@@ -86,28 +90,42 @@ module ActiveSupport
86
90
  instance = run!
87
91
  begin
88
92
  yield
93
+ rescue => error
94
+ error_reporter.report(error, handled: false)
95
+ raise
89
96
  ensure
90
97
  instance.complete!
91
98
  end
92
99
  end
93
100
 
94
- class << self # :nodoc:
95
- attr_accessor :active
101
+ def self.perform # :nodoc:
102
+ instance = new
103
+ instance.run
104
+ begin
105
+ yield
106
+ ensure
107
+ instance.complete
108
+ end
96
109
  end
97
110
 
98
- def self.inherited(other) # :nodoc:
99
- super
100
- other.active = Concurrent::Hash.new
111
+ def self.error_reporter
112
+ @error_reporter ||= ActiveSupport::ErrorReporter.new
101
113
  end
102
114
 
103
- self.active = Concurrent::Hash.new
115
+ def self.active_key # :nodoc:
116
+ @active_key ||= :"active_execution_wrapper_#{object_id}"
117
+ end
104
118
 
105
119
  def self.active? # :nodoc:
106
- @active[Thread.current]
120
+ IsolatedExecutionState.key?(active_key)
107
121
  end
108
122
 
109
123
  def run! # :nodoc:
110
- self.class.active[Thread.current] = true
124
+ IsolatedExecutionState[self.class.active_key] = self
125
+ run
126
+ end
127
+
128
+ def run # :nodoc:
111
129
  run_callbacks(:run)
112
130
  end
113
131
 
@@ -116,9 +134,13 @@ module ActiveSupport
116
134
  #
117
135
  # Where possible, prefer +wrap+.
118
136
  def complete!
119
- run_callbacks(:complete)
137
+ complete
120
138
  ensure
121
- self.class.active.delete Thread.current
139
+ IsolatedExecutionState.delete(self.class.active_key)
140
+ end
141
+
142
+ def complete # :nodoc:
143
+ run_callbacks(:complete)
122
144
  end
123
145
 
124
146
  private
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveSupport::Executor::TestHelper # :nodoc:
4
+ def run(...)
5
+ Rails.application.executor.perform { super }
6
+ end
7
+ end
@@ -2,8 +2,18 @@
2
2
 
3
3
  module ActiveSupport
4
4
  module ForkTracker # :nodoc:
5
+ module ModernCoreExt
6
+ def _fork
7
+ pid = super
8
+ if pid == 0
9
+ ForkTracker.check!
10
+ end
11
+ pid
12
+ end
13
+ end
14
+
5
15
  module CoreExt
6
- def fork(*)
16
+ def fork(...)
7
17
  if block_given?
8
18
  super do
9
19
  ForkTracker.check!
@@ -16,17 +26,11 @@ module ActiveSupport
16
26
  pid
17
27
  end
18
28
  end
19
- ruby2_keywords(:fork) if respond_to?(:ruby2_keywords, true)
20
29
  end
21
30
 
22
31
  module CoreExtPrivate
23
32
  include CoreExt
24
-
25
- private
26
- def fork(*)
27
- super
28
- end
29
- ruby2_keywords(:fork) if respond_to?(:ruby2_keywords, true)
33
+ private :fork
30
34
  end
31
35
 
32
36
  @pid = Process.pid
@@ -34,15 +38,18 @@ module ActiveSupport
34
38
 
35
39
  class << self
36
40
  def check!
37
- if @pid != Process.pid
41
+ new_pid = Process.pid
42
+ if @pid != new_pid
38
43
  @callbacks.each(&:call)
39
- @pid = Process.pid
44
+ @pid = new_pid
40
45
  end
41
46
  end
42
47
 
43
48
  def hook!
44
- if Process.respond_to?(:fork)
45
- ::Object.prepend(CoreExtPrivate)
49
+ if Process.respond_to?(:_fork) # Ruby 3.1+
50
+ ::Process.singleton_class.prepend(ModernCoreExt)
51
+ elsif Process.respond_to?(:fork)
52
+ ::Object.prepend(CoreExtPrivate) if RUBY_VERSION < "3.0"
46
53
  ::Kernel.prepend(CoreExtPrivate)
47
54
  ::Kernel.singleton_class.prepend(CoreExt)
48
55
  ::Process.singleton_class.prepend(CoreExt)
@@ -1,16 +1,16 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActiveSupport
4
- # Returns the version of the currently loaded Active Support as a <tt>Gem::Version</tt>.
4
+ # Returns the currently loaded version of Active Support as a <tt>Gem::Version</tt>.
5
5
  def self.gem_version
6
6
  Gem::Version.new VERSION::STRING
7
7
  end
8
8
 
9
9
  module VERSION
10
- MAJOR = 6
11
- MINOR = 1
12
- TINY = 4
13
- PRE = "1"
10
+ MAJOR = 7
11
+ MINOR = 0
12
+ TINY = 8
13
+ PRE = "3"
14
14
 
15
15
  STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
16
16
  end
@@ -65,7 +65,7 @@ module ActiveSupport
65
65
  self
66
66
  end
67
67
 
68
- def initialize(constructor = {})
68
+ def initialize(constructor = nil)
69
69
  if constructor.respond_to?(:to_hash)
70
70
  super()
71
71
  update(constructor)
@@ -73,6 +73,8 @@ module ActiveSupport
73
73
  hash = constructor.is_a?(Hash) ? constructor : constructor.to_hash
74
74
  self.default = hash.default if hash.default
75
75
  self.default_proc = hash.default_proc if hash.default_proc
76
+ elsif constructor.nil?
77
+ super()
76
78
  else
77
79
  super(constructor)
78
80
  end
@@ -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
@@ -77,7 +77,7 @@ module I18n
77
77
 
78
78
  def self.forward_raise_on_missing_translations_config(app)
79
79
  ActiveSupport.on_load(:action_view) do
80
- self.raise_on_missing_translations = app.config.i18n.raise_on_missing_translations
80
+ ActionView::Helpers::TranslationHelper.raise_on_missing_translations = app.config.i18n.raise_on_missing_translations
81
81
  end
82
82
 
83
83
  ActiveSupport.on_load(:action_controller) do
@@ -16,13 +16,13 @@ module ActiveSupport
16
16
  # inflect.plural /^(ox)$/i, '\1\2en'
17
17
  # inflect.singular /^(ox)en/i, '\1'
18
18
  #
19
- # inflect.irregular 'octopus', 'octopi'
19
+ # inflect.irregular 'cactus', 'cacti'
20
20
  #
21
21
  # inflect.uncountable 'equipment'
22
22
  # end
23
23
  #
24
24
  # New rules are added at the top. So in the example above, the irregular
25
- # 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
26
26
  # singularization rules that is runs. This guarantees that your rules run
27
27
  # before any of the rules that may already have been loaded.
28
28
  class Inflections
@@ -64,6 +64,13 @@ module ActiveSupport
64
64
  @__instance__[locale] ||= new
65
65
  end
66
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
+
67
74
  attr_reader :plurals, :singulars, :uncountables, :humans, :acronyms
68
75
 
69
76
  attr_reader :acronyms_camelize_regex, :acronyms_underscore_regex # :nodoc:
@@ -160,7 +167,7 @@ module ActiveSupport
160
167
  # regular expressions. You simply pass the irregular in singular and
161
168
  # plural form.
162
169
  #
163
- # irregular 'octopus', 'octopi'
170
+ # irregular 'cactus', 'cacti'
164
171
  # irregular 'person', 'people'
165
172
  def irregular(singular, plural)
166
173
  @uncountables.delete(singular)
@@ -215,15 +222,24 @@ module ActiveSupport
215
222
  # Clears the loaded inflections within a given scope (default is
216
223
  # <tt>:all</tt>). Give the scope as a symbol of the inflection type, the
217
224
  # options are: <tt>:plurals</tt>, <tt>:singulars</tt>, <tt>:uncountables</tt>,
218
- # <tt>:humans</tt>.
225
+ # <tt>:humans</tt>, <tt>:acronyms</tt>.
219
226
  #
220
227
  # clear :all
221
228
  # clear :plurals
222
229
  def clear(scope = :all)
223
230
  case scope
224
231
  when :all
225
- @plurals, @singulars, @uncountables, @humans = [], [], Uncountables.new, []
226
- 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
227
243
  instance_variable_set "@#{scope}", []
228
244
  end
229
245
  end
@@ -248,7 +264,7 @@ module ActiveSupport
248
264
  if block_given?
249
265
  yield Inflections.instance(locale)
250
266
  else
251
- Inflections.instance(locale)
267
+ Inflections.instance_or_fallback(locale)
252
268
  end
253
269
  end
254
270
  end
@@ -68,13 +68,17 @@ module ActiveSupport
68
68
  # camelize(underscore('SSLError')) # => "SslError"
69
69
  def camelize(term, uppercase_first_letter = true)
70
70
  string = term.to_s
71
- if uppercase_first_letter
72
- 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 }
73
74
  else
74
- 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
75
81
  end
76
- string.gsub!(/(?:_|(\/))([a-z\d]*)/i) { "#{$1}#{inflections.acronyms[$2] || $2.capitalize}" }
77
- string.gsub!("/", "::")
78
82
  string
79
83
  end
80
84
 
@@ -90,11 +94,10 @@ module ActiveSupport
90
94
  #
91
95
  # camelize(underscore('SSLError')) # => "SslError"
92
96
  def underscore(camel_cased_word)
93
- 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)
94
98
  word = camel_cased_word.to_s.gsub("::", "/")
95
99
  word.gsub!(inflections.acronyms_underscore_regex) { "#{$1 && '_' }#{$2.downcase}" }
96
- word.gsub!(/([A-Z\d]+)([A-Z][a-z])/, '\1_\2')
97
- 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) << "_" }
98
101
  word.tr!("-", "_")
99
102
  word.downcase!
100
103
  word
@@ -106,7 +109,7 @@ module ActiveSupport
106
109
  #
107
110
  # * Applies human inflection rules to the argument.
108
111
  # * Deletes leading underscores, if any.
109
- # * Removes a "_id" suffix if present.
112
+ # * Removes an "_id" suffix if present.
110
113
  # * Replaces underscores with spaces, if any.
111
114
  # * Downcases all words except acronyms.
112
115
  # * Capitalizes the first word.
@@ -120,7 +123,7 @@ module ActiveSupport
120
123
  # humanize('author_id') # => "Author"
121
124
  # humanize('author_id', capitalize: false) # => "author"
122
125
  # humanize('_id') # => "Id"
123
- # humanize('author_id', keep_id_suffix: true) # => "Author Id"
126
+ # humanize('author_id', keep_id_suffix: true) # => "Author id"
124
127
  #
125
128
  # If "SSL" was defined to be an acronym:
126
129
  #
@@ -131,18 +134,22 @@ module ActiveSupport
131
134
 
132
135
  inflections.humans.each { |(rule, replacement)| break if result.sub!(rule, replacement) }
133
136
 
134
- result.sub!(/\A_+/, "")
135
- unless keep_id_suffix
136
- result.delete_suffix!("_id")
137
- end
138
137
  result.tr!("_", " ")
138
+ result.lstrip!
139
+ if !keep_id_suffix && lower_case_and_underscored_word&.end_with?("_id")
140
+ result.delete_suffix!(" id")
141
+ end
139
142
 
140
- result.gsub!(/([a-z\d]*)/i) do |match|
141
- "#{inflections.acronyms[match.downcase] || match.downcase}"
143
+ result.gsub!(/([a-z\d]+)/i) do |match|
144
+ match.downcase!
145
+ inflections.acronyms[match] || match
142
146
  end
143
147
 
144
148
  if capitalize
145
- result.sub!(/\A\w/) { |match| match.upcase }
149
+ result.sub!(/\A\w/) do |match|
150
+ match.upcase!
151
+ match
152
+ end
146
153
  end
147
154
 
148
155
  result
@@ -165,8 +172,6 @@ module ActiveSupport
165
172
  # optional parameter +keep_id_suffix+ to true.
166
173
  # By default, this parameter is false.
167
174
  #
168
- # +titleize+ is also aliased as +titlecase+.
169
- #
170
175
  # titleize('man from the boondocks') # => "Man From The Boondocks"
171
176
  # titleize('x-men: the last stand') # => "X Men: The Last Stand"
172
177
  # titleize('TheManWithoutAPast') # => "The Man Without A Past"
@@ -189,8 +194,8 @@ module ActiveSupport
189
194
  end
190
195
 
191
196
  # Creates a class name from a plural table name like Rails does for table
192
- # names to models. Note that this returns a string and not a Class (To
193
- # convert to an actual class follow +classify+ with #constantize).
197
+ # names to models. Note that this returns a string and not a Class. (To
198
+ # convert to an actual class follow +classify+ with #constantize.)
194
199
  #
195
200
  # classify('ham_and_eggs') # => "HamAndEgg"
196
201
  # classify('posts') # => "Post"
@@ -270,38 +275,7 @@ module ActiveSupport
270
275
  # NameError is raised when the name is not in CamelCase or the constant is
271
276
  # unknown.
272
277
  def constantize(camel_cased_word)
273
- if camel_cased_word.blank? || !camel_cased_word.include?("::")
274
- Object.const_get(camel_cased_word)
275
- else
276
- names = camel_cased_word.split("::")
277
-
278
- # Trigger a built-in NameError exception including the ill-formed constant in the message.
279
- Object.const_get(camel_cased_word) if names.empty?
280
-
281
- # Remove the first blank element in case of '::ClassName' notation.
282
- names.shift if names.size > 1 && names.first.empty?
283
-
284
- names.inject(Object) do |constant, name|
285
- if constant == Object
286
- constant.const_get(name)
287
- else
288
- candidate = constant.const_get(name)
289
- next candidate if constant.const_defined?(name, false)
290
- next candidate unless Object.const_defined?(name)
291
-
292
- # Go down the ancestors to check if it is owned directly. The check
293
- # stops when we reach Object or the end of ancestors tree.
294
- constant = constant.ancestors.inject(constant) do |const, ancestor|
295
- break const if ancestor == Object
296
- break ancestor if ancestor.const_defined?(name, false)
297
- const
298
- end
299
-
300
- # owner is in Object, so raise
301
- constant.const_get(name, false)
302
- end
303
- end
304
- end
278
+ Object.const_get(camel_cased_word)
305
279
  end
306
280
 
307
281
  # Tries to find a constant with the name specified in the argument string.
@@ -385,8 +359,8 @@ module ActiveSupport
385
359
  # If passed an optional +locale+ parameter, the uncountables will be
386
360
  # found for that locale.
387
361
  #
388
- # apply_inflections('post', inflections.plurals, :en) # => "posts"
389
- # apply_inflections('posts', inflections.singulars, :en) # => "post"
362
+ # apply_inflections('post', inflections.plurals, :en) # => "posts"
363
+ # apply_inflections('posts', inflections.singulars, :en) # => "post"
390
364
  def apply_inflections(word, rules, locale = :en)
391
365
  result = word.to_s.dup
392
366
 
@@ -59,7 +59,7 @@ module ActiveSupport
59
59
  # transliterate('Jürgen', locale: :de)
60
60
  # # => "Juergen"
61
61
  #
62
- # Transliteration is restricted to UTF-8, US-ASCII and GB18030 strings
62
+ # Transliteration is restricted to UTF-8, US-ASCII, and GB18030 strings.
63
63
  # Other encodings will raise an ArgumentError.
64
64
  def transliterate(string, replacement = "?", locale: nil)
65
65
  string = string.dup if string.frozen?
@@ -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
@@ -22,8 +22,8 @@ module ActiveSupport
22
22
  Encoding.json_encoder.new(options).encode(value)
23
23
  end
24
24
 
25
- module Encoding #:nodoc:
26
- class JSONGemEncoder #:nodoc:
25
+ module Encoding # :nodoc:
26
+ class JSONGemEncoder # :nodoc:
27
27
  attr_reader :options
28
28
 
29
29
  def initialize(options = nil)
@@ -51,7 +51,7 @@ module ActiveSupport
51
51
  ESCAPE_REGEX_WITHOUT_HTML_ENTITIES = /[\u2028\u2029]/u
52
52
 
53
53
  # This class wraps all the strings we see and does the extra escaping
54
- class EscapedString < String #:nodoc:
54
+ class EscapedString < String # :nodoc:
55
55
  def to_json(*)
56
56
  if Encoding.escape_html_entities_in_json
57
57
  s = super