activesupport 7.2.0 → 8.0.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (49) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +45 -210
  3. data/lib/active_support/backtrace_cleaner.rb +1 -1
  4. data/lib/active_support/benchmark.rb +21 -0
  5. data/lib/active_support/benchmarkable.rb +3 -2
  6. data/lib/active_support/broadcast_logger.rb +14 -14
  7. data/lib/active_support/cache/file_store.rb +12 -2
  8. data/lib/active_support/cache/memory_store.rb +6 -2
  9. data/lib/active_support/cache/redis_cache_store.rb +5 -2
  10. data/lib/active_support/cache.rb +16 -11
  11. data/lib/active_support/callbacks.rb +1 -2
  12. data/lib/active_support/class_attribute.rb +26 -0
  13. data/lib/active_support/code_generator.rb +9 -0
  14. data/lib/active_support/concurrency/share_lock.rb +0 -1
  15. data/lib/active_support/configuration_file.rb +15 -6
  16. data/lib/active_support/core_ext/benchmark.rb +6 -9
  17. data/lib/active_support/core_ext/class/attribute.rb +10 -19
  18. data/lib/active_support/core_ext/date/conversions.rb +2 -0
  19. data/lib/active_support/core_ext/date_and_time/compatibility.rb +2 -2
  20. data/lib/active_support/core_ext/enumerable.rb +8 -3
  21. data/lib/active_support/core_ext/hash/except.rb +0 -12
  22. data/lib/active_support/core_ext/object/json.rb +15 -9
  23. data/lib/active_support/core_ext/time/calculations.rb +14 -2
  24. data/lib/active_support/core_ext/time/conversions.rb +2 -0
  25. data/lib/active_support/current_attributes.rb +8 -3
  26. data/lib/active_support/deprecation/reporting.rb +2 -2
  27. data/lib/active_support/deprecation.rb +1 -1
  28. data/lib/active_support/encrypted_configuration.rb +20 -2
  29. data/lib/active_support/encrypted_file.rb +1 -1
  30. data/lib/active_support/error_reporter.rb +25 -1
  31. data/lib/active_support/gem_version.rb +3 -3
  32. data/lib/active_support/hash_with_indifferent_access.rb +16 -12
  33. data/lib/active_support/i18n_railtie.rb +19 -10
  34. data/lib/active_support/isolated_execution_state.rb +0 -1
  35. data/lib/active_support/json/encoding.rb +2 -2
  36. data/lib/active_support/notifications.rb +2 -2
  37. data/lib/active_support/number_helper.rb +22 -0
  38. data/lib/active_support/railtie.rb +4 -0
  39. data/lib/active_support/subscriber.rb +1 -0
  40. data/lib/active_support/tagged_logging.rb +5 -0
  41. data/lib/active_support/testing/assertions.rb +72 -21
  42. data/lib/active_support/testing/isolation.rb +2 -2
  43. data/lib/active_support/testing/parallelization/server.rb +3 -0
  44. data/lib/active_support/testing/strict_warnings.rb +3 -0
  45. data/lib/active_support/testing/time_helpers.rb +2 -1
  46. data/lib/active_support/time_with_zone.rb +21 -12
  47. data/lib/active_support/values/time_zone.rb +11 -9
  48. data/lib/active_support.rb +10 -2
  49. metadata +40 -10
@@ -19,11 +19,20 @@ module ActiveSupport
19
19
  end
20
20
 
21
21
  def parse(context: nil, **options)
22
- source = render(context)
23
- if YAML.respond_to?(:unsafe_load)
24
- YAML.unsafe_load(source, **options) || {}
22
+ source = @content.include?("<%") ? render(context) : @content
23
+
24
+ if source == @content
25
+ if YAML.respond_to?(:unsafe_load)
26
+ YAML.unsafe_load_file(@content_path, **options) || {}
27
+ else
28
+ YAML.load_file(@content_path, **options) || {}
29
+ end
25
30
  else
26
- YAML.load(source, **options) || {}
31
+ if YAML.respond_to?(:unsafe_load)
32
+ YAML.unsafe_load(source, **options) || {}
33
+ else
34
+ YAML.load(source, **options) || {}
35
+ end
27
36
  end
28
37
  rescue Psych::SyntaxError => error
29
38
  raise "YAML syntax error occurred while parsing #{@content_path}. " \
@@ -33,8 +42,7 @@ module ActiveSupport
33
42
 
34
43
  private
35
44
  def read(content_path)
36
- require "yaml"
37
- require "erb"
45
+ require "yaml" unless defined?(YAML)
38
46
 
39
47
  File.read(content_path).tap do |content|
40
48
  if content.include?("\u00A0")
@@ -44,6 +52,7 @@ module ActiveSupport
44
52
  end
45
53
 
46
54
  def render(context)
55
+ require "erb" unless defined?(ERB)
47
56
  erb = ERB.new(@content).tap { |e| e.filename = @content_path }
48
57
  context ? erb.result(context) : erb.result
49
58
  end
@@ -3,14 +3,11 @@
3
3
  require "benchmark"
4
4
 
5
5
  class << Benchmark
6
- # Benchmark realtime in milliseconds.
7
- #
8
- # Benchmark.realtime { User.all }
9
- # # => 8.0e-05
10
- #
11
- # Benchmark.ms { User.all }
12
- # # => 0.074
13
- def ms(&block)
14
- 1000 * realtime(&block)
6
+ def ms(&block) # :nodoc
7
+ # NOTE: Please also remove the Active Support `benchmark` dependency when removing this
8
+ ActiveSupport.deprecator.warn <<~TEXT
9
+ `Benchmark.ms` is deprecated and will be removed in Rails 8.1 without replacement.
10
+ TEXT
11
+ ActiveSupport::Benchmark.realtime(:float_millisecond, &block)
15
12
  end
16
13
  end
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "active_support/core_ext/module/redefine_method"
4
+ require "active_support/class_attribute"
4
5
 
5
6
  class Class
6
7
  # Declare a class-level attribute whose value is inheritable by subclasses.
@@ -91,24 +92,16 @@ class Class
91
92
  raise TypeError, "#{name.inspect} is not a symbol nor a string"
92
93
  end
93
94
 
94
- class_methods << <<~RUBY # In case the method exists and is not public
95
- silence_redefinition_of_method def #{name}
96
- end
97
- RUBY
98
-
99
- methods << <<~RUBY if instance_reader
100
- silence_redefinition_of_method def #{name}
101
- defined?(@#{name}) ? @#{name} : self.class.#{name}
102
- end
103
- RUBY
95
+ name = name.to_sym
96
+ ::ActiveSupport::ClassAttribute.redefine(self, name, default)
104
97
 
105
- class_methods << <<~RUBY
106
- silence_redefinition_of_method def #{name}=(value)
107
- redefine_method(:#{name}) { value } if singleton_class?
108
- redefine_singleton_method(:#{name}) { value }
109
- value
110
- end
111
- RUBY
98
+ unless singleton_class?
99
+ methods << <<~RUBY if instance_reader
100
+ silence_redefinition_of_method def #{name}
101
+ defined?(@#{name}) ? @#{name} : self.class.#{name}
102
+ end
103
+ RUBY
104
+ end
112
105
 
113
106
  methods << <<~RUBY if instance_writer
114
107
  silence_redefinition_of_method(:#{name}=)
@@ -125,7 +118,5 @@ class Class
125
118
 
126
119
  location = caller_locations(1, 1).first
127
120
  class_eval(["class << self", *class_methods, "end", *methods].join(";").tr("\n", ";"), location.path, location.lineno)
128
-
129
- attrs.each { |name| public_send("#{name}=", default) }
130
121
  end
131
122
  end
@@ -17,6 +17,7 @@ class Date
17
17
  date.strftime("%B #{day_format}, %Y") # => "April 25th, 2007"
18
18
  },
19
19
  rfc822: "%d %b %Y",
20
+ rfc2822: "%d %b %Y",
20
21
  iso8601: lambda { |date| date.iso8601 }
21
22
  }
22
23
 
@@ -34,6 +35,7 @@ class Date
34
35
  # date.to_fs(:long) # => "November 10, 2007"
35
36
  # date.to_fs(:long_ordinal) # => "November 10th, 2007"
36
37
  # date.to_fs(:rfc822) # => "10 Nov 2007"
38
+ # date.to_fs(:rfc2822) # => "10 Nov 2007"
37
39
  # date.to_fs(:iso8601) # => "2007-11-10"
38
40
  #
39
41
  # == Adding your own date formats to to_fs
@@ -26,8 +26,8 @@ module DateAndTime
26
26
  # Only warn once, the first time the value is used (which should
27
27
  # be the first time #to_time is called).
28
28
  ActiveSupport.deprecator.warn(
29
- "to_time will always preserve the timezone offset of the receiver in Rails 8.0. " \
30
- "To opt in to the new behavior, set `ActiveSupport.to_time_preserves_timezone = true`."
29
+ "`to_time` will always preserve the receiver timezone rather than system local time in Rails 8.0." \
30
+ "To opt in to the new behavior, set `config.active_support.to_time_preserves_timezone = :zone`."
31
31
  )
32
32
 
33
33
  @@preserve_timezone = false
@@ -192,9 +192,14 @@ module Enumerable
192
192
  # # => [ Person.find(1), Person.find(5), Person.find(3) ]
193
193
  #
194
194
  # If the +series+ include keys that have no corresponding element in the Enumerable, these are ignored.
195
- # If the Enumerable has additional elements that aren't named in the +series+, these are not included in the result.
196
- def in_order_of(key, series)
197
- group_by(&key).values_at(*series).flatten(1).compact
195
+ # If the Enumerable has additional elements that aren't named in the +series+, these are not included in the result, unless
196
+ # the +filter+ option is set to +false+.
197
+ def in_order_of(key, series, filter: true)
198
+ if filter
199
+ group_by(&key).values_at(*series).flatten(1).compact
200
+ else
201
+ sort_by { |v| series.index(v.public_send(key)) || series.size }.compact
202
+ end
198
203
  end
199
204
 
200
205
  # Returns the sole item in the enumerable. If there are no items, or more
@@ -1,18 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class Hash
4
- # Returns a hash that includes everything except given keys.
5
- # hash = { a: true, b: false, c: nil }
6
- # hash.except(:c) # => { a: true, b: false }
7
- # hash.except(:a, :b) # => { c: nil }
8
- # hash # => { a: true, b: false, c: nil }
9
- #
10
- # This is useful for limiting a set of parameters to everything but a few known toggles:
11
- # @person.update(params[:person].except(:admin))
12
- def except(*keys)
13
- slice(*self.keys - keys)
14
- end unless method_defined?(:except)
15
-
16
4
  # Removes the given keys from hash and returns it.
17
5
  # hash = { a: true, b: false, c: nil }
18
6
  # hash.except!(:c) # => { a: true, b: false }
@@ -65,11 +65,9 @@ class Object
65
65
  end
66
66
  end
67
67
 
68
- if RUBY_VERSION >= "3.2"
69
- class Data # :nodoc:
70
- def as_json(options = nil)
71
- to_h.as_json(options)
72
- end
68
+ class Data # :nodoc:
69
+ def as_json(options = nil)
70
+ to_h.as_json(options)
73
71
  end
74
72
  end
75
73
 
@@ -105,7 +103,7 @@ end
105
103
 
106
104
  class Symbol
107
105
  def as_json(options = nil) # :nodoc:
108
- to_s
106
+ name
109
107
  end
110
108
  end
111
109
 
@@ -164,7 +162,12 @@ end
164
162
 
165
163
  class Array
166
164
  def as_json(options = nil) # :nodoc:
167
- map { |v| options ? v.as_json(options.dup) : v.as_json }
165
+ if options
166
+ options = options.dup.freeze unless options.frozen?
167
+ map { |v| v.as_json(options) }
168
+ else
169
+ map { |v| v.as_json }
170
+ end
168
171
  end
169
172
  end
170
173
 
@@ -184,8 +187,11 @@ class Hash
184
187
  end
185
188
 
186
189
  result = {}
187
- subset.each do |k, v|
188
- result[k.to_s] = options ? v.as_json(options.dup) : v.as_json
190
+ if options
191
+ options = options.dup.freeze unless options.frozen?
192
+ subset.each { |k, v| result[k.to_s] = v.as_json(options) }
193
+ else
194
+ subset.each { |k, v| result[k.to_s] = v.as_json }
189
195
  end
190
196
  result
191
197
  end
@@ -147,6 +147,13 @@ class Time
147
147
  elsif zone.respond_to?(:utc_to_local)
148
148
  new_time = ::Time.new(new_year, new_month, new_day, new_hour, new_min, new_sec, zone)
149
149
 
150
+ # Some versions of Ruby have a bug where Time.new with a zone object and
151
+ # fractional seconds will end up with a broken utc_offset.
152
+ # This is fixed in Ruby 3.3.1 and 3.2.4
153
+ unless new_time.utc_offset.integer?
154
+ new_time += 0
155
+ end
156
+
150
157
  # When there are two occurrences of a nominal time due to DST ending,
151
158
  # `Time.new` chooses the first chronological occurrence (the one with a
152
159
  # larger UTC offset). However, for `change`, we want to choose the
@@ -217,8 +224,13 @@ class Time
217
224
  # Returns a new Time representing the time a number of seconds since the instance time
218
225
  def since(seconds)
219
226
  self + seconds
220
- rescue
221
- to_datetime.since(seconds)
227
+ rescue TypeError
228
+ result = to_datetime.since(seconds)
229
+ ActiveSupport.deprecator.warn(
230
+ "Passing an instance of #{seconds.class} to #{self.class}#since is deprecated. This behavior will raise " \
231
+ "a `TypeError` in Rails 8.1."
232
+ )
233
+ result
222
234
  end
223
235
  alias :in :since
224
236
 
@@ -22,6 +22,7 @@ class Time
22
22
  offset_format = time.formatted_offset(false)
23
23
  time.strftime("%a, %d %b %Y %H:%M:%S #{offset_format}")
24
24
  },
25
+ rfc2822: lambda { |time| time.rfc2822 },
25
26
  iso8601: lambda { |time| time.iso8601 }
26
27
  }
27
28
 
@@ -40,6 +41,7 @@ class Time
40
41
  # time.to_fs(:long) # => "January 18, 2007 06:10"
41
42
  # time.to_fs(:long_ordinal) # => "January 18th, 2007 06:10"
42
43
  # time.to_fs(:rfc822) # => "Thu, 18 Jan 2007 06:10:17 -0600"
44
+ # time.to_fs(:rfc2822) # => "Thu, 18 Jan 2007 06:10:17 -0600"
43
45
  # time.to_fs(:iso8601) # => "2007-01-18T06:10:17-06:00"
44
46
  #
45
47
  # == Adding your own time formats to +to_fs+
@@ -95,6 +95,8 @@ module ActiveSupport
95
95
 
96
96
  INVALID_ATTRIBUTE_NAMES = [:set, :reset, :resets, :instance, :before_reset, :after_reset, :reset_all, :clear_all] # :nodoc:
97
97
 
98
+ NOT_SET = Object.new.freeze # :nodoc:
99
+
98
100
  class << self
99
101
  # Returns singleton instance for this class in this thread. If none exists, one is created.
100
102
  def instance
@@ -109,7 +111,7 @@ module ActiveSupport
109
111
  # is a proc or lambda, it will be called whenever an instance is
110
112
  # constructed. Otherwise, the value will be duplicated with +#dup+.
111
113
  # Default values are re-assigned when the attributes are reset.
112
- def attribute(*names, default: nil)
114
+ def attribute(*names, default: NOT_SET)
113
115
  invalid_attribute_names = names.map(&:to_sym) & INVALID_ATTRIBUTE_NAMES
114
116
  if invalid_attribute_names.any?
115
117
  raise ArgumentError, "Restricted attribute names: #{invalid_attribute_names.join(", ")}"
@@ -182,6 +184,7 @@ module ActiveSupport
182
184
  end
183
185
 
184
186
  def method_added(name)
187
+ super
185
188
  return if name == :initialize
186
189
  return unless public_method_defined?(name)
187
190
  return if respond_to?(name, true)
@@ -220,8 +223,10 @@ module ActiveSupport
220
223
 
221
224
  private
222
225
  def resolve_defaults
223
- defaults.transform_values do |value|
224
- Proc === value ? value.call : value.dup
226
+ defaults.each_with_object({}) do |(key, value), result|
227
+ if value != NOT_SET
228
+ result[key] = Proc === value ? value.call : value.dup
229
+ end
225
230
  end
226
231
  end
227
232
  end
@@ -168,8 +168,8 @@ module ActiveSupport
168
168
  end
169
169
  end
170
170
 
171
- RAILS_GEM_ROOT = File.expand_path("../../../..", __dir__) + "/"
172
- LIB_DIR = RbConfig::CONFIG["libdir"]
171
+ RAILS_GEM_ROOT = File.expand_path("../../../..", __dir__) + "/" # :nodoc:
172
+ LIB_DIR = RbConfig::CONFIG["libdir"] # :nodoc:
173
173
 
174
174
  def ignored_callstack?(path)
175
175
  path.start_with?(RAILS_GEM_ROOT, LIB_DIR) || path.include?("<internal:")
@@ -68,7 +68,7 @@ module ActiveSupport
68
68
  # and the second is a library name.
69
69
  #
70
70
  # ActiveSupport::Deprecation.new('2.0', 'MyLibrary')
71
- def initialize(deprecation_horizon = "8.0", gem_name = "Rails")
71
+ def initialize(deprecation_horizon = "8.1", gem_name = "Rails")
72
72
  self.gem_name = gem_name
73
73
  self.deprecation_horizon = deprecation_horizon
74
74
  # By default, warnings are not silenced and debugging is off.
@@ -43,6 +43,12 @@ module ActiveSupport
43
43
  end
44
44
  end
45
45
 
46
+ class InvalidKeyError < RuntimeError
47
+ def initialize(content_path, key)
48
+ super "Key '#{key}' is invalid, it must respond to '#to_sym' from configuration in '#{content_path}'."
49
+ end
50
+ end
51
+
46
52
  delegate_missing_to :options
47
53
 
48
54
  def initialize(config_path:, key_path:, env_key:, raise_if_missing_key:)
@@ -61,7 +67,11 @@ module ActiveSupport
61
67
  end
62
68
 
63
69
  def validate! # :nodoc:
64
- deserialize(read)
70
+ deserialize(read).each_key do |key|
71
+ key.to_sym
72
+ rescue NoMethodError
73
+ raise InvalidKeyError.new(content_path, key)
74
+ end
65
75
  end
66
76
 
67
77
  # Returns the decrypted content as a Hash with symbolized keys.
@@ -73,7 +83,7 @@ module ActiveSupport
73
83
  # # => { some_secret: 123, some_namespace: { another_secret: 789 } }
74
84
  #
75
85
  def config
76
- @config ||= deserialize(read).deep_symbolize_keys
86
+ @config ||= deep_symbolize_keys(deserialize(read))
77
87
  end
78
88
 
79
89
  def inspect # :nodoc:
@@ -81,6 +91,14 @@ module ActiveSupport
81
91
  end
82
92
 
83
93
  private
94
+ def deep_symbolize_keys(hash)
95
+ hash.deep_transform_keys do |key|
96
+ key.to_sym
97
+ rescue NoMethodError
98
+ raise InvalidKeyError.new(content_path, key)
99
+ end
100
+ end
101
+
84
102
  def deep_transform(hash)
85
103
  return hash unless hash.is_a?(Hash)
86
104
 
@@ -69,7 +69,7 @@ module ActiveSupport
69
69
  # decrypted or verified.
70
70
  def read
71
71
  if !key.nil? && content_path.exist?
72
- decrypt content_path.binread
72
+ decrypt content_path.binread.strip
73
73
  else
74
74
  raise MissingContentError, content_path
75
75
  end
@@ -144,9 +144,9 @@ module ActiveSupport
144
144
  #
145
145
  def unexpected(error, severity: :warning, context: {}, source: DEFAULT_SOURCE)
146
146
  error = RuntimeError.new(error) if error.is_a?(String)
147
- error.set_backtrace(caller(1)) if error.backtrace.nil?
148
147
 
149
148
  if @debug_mode
149
+ ensure_backtrace(error)
150
150
  raise UnexpectedError, "#{error.class.name}: #{error.message}", error.backtrace, cause: error
151
151
  else
152
152
  report(error, handled: true, severity: severity, context: context, source: source)
@@ -209,6 +209,7 @@ module ActiveSupport
209
209
  #
210
210
  def report(error, handled: true, severity: handled ? :warning : :error, context: {}, source: DEFAULT_SOURCE)
211
211
  return if error.instance_variable_defined?(:@__rails_error_reported)
212
+ ensure_backtrace(error)
212
213
 
213
214
  unless SEVERITIES.include?(severity)
214
215
  raise ArgumentError, "severity must be one of #{SEVERITIES.map(&:inspect).join(", ")}, got: #{severity.inspect}"
@@ -237,5 +238,28 @@ module ActiveSupport
237
238
 
238
239
  nil
239
240
  end
241
+
242
+ private
243
+ def ensure_backtrace(error)
244
+ return if error.frozen? # re-raising won't add a backtrace
245
+ return unless error.backtrace.nil?
246
+
247
+ begin
248
+ # We could use Exception#set_backtrace, but until Ruby 3.4
249
+ # it only support setting `Exception#backtrace` and not
250
+ # `Exception#backtrace_locations`. So raising the exception
251
+ # is a good way to build a real backtrace.
252
+ raise error
253
+ rescue error.class => error
254
+ end
255
+
256
+ count = 0
257
+ while error.backtrace_locations.first&.path == __FILE__
258
+ count += 1
259
+ error.backtrace_locations.shift
260
+ end
261
+
262
+ error.backtrace.shift(count)
263
+ end
240
264
  end
241
265
  end
@@ -7,10 +7,10 @@ module ActiveSupport
7
7
  end
8
8
 
9
9
  module VERSION
10
- MAJOR = 7
11
- MINOR = 2
10
+ MAJOR = 8
11
+ MINOR = 0
12
12
  TINY = 0
13
- PRE = nil
13
+ PRE = "beta1"
14
14
 
15
15
  STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
16
16
  end
@@ -378,13 +378,10 @@ module ActiveSupport
378
378
 
379
379
  # Convert to a regular hash with string keys.
380
380
  def to_hash
381
- _new_hash = Hash.new
382
- set_defaults(_new_hash)
383
-
384
- each do |key, value|
385
- _new_hash[key] = convert_value(value, conversion: :to_hash)
386
- end
387
- _new_hash
381
+ copy = Hash[self]
382
+ copy.transform_values! { |v| convert_value_to_hash(v) }
383
+ set_defaults(copy)
384
+ copy
388
385
  end
389
386
 
390
387
  def to_proc
@@ -398,11 +395,7 @@ module ActiveSupport
398
395
 
399
396
  def convert_value(value, conversion: nil)
400
397
  if value.is_a? Hash
401
- if conversion == :to_hash
402
- value.to_hash
403
- else
404
- value.nested_under_indifferent_access
405
- end
398
+ value.nested_under_indifferent_access
406
399
  elsif value.is_a?(Array)
407
400
  if conversion != :assignment || value.frozen?
408
401
  value = value.dup
@@ -413,6 +406,17 @@ module ActiveSupport
413
406
  end
414
407
  end
415
408
 
409
+ def convert_value_to_hash(value)
410
+ if value.is_a? Hash
411
+ value.to_hash
412
+ elsif value.is_a?(Array)
413
+ value.map { |e| convert_value_to_hash(e) }
414
+ else
415
+ value
416
+ end
417
+ end
418
+
419
+
416
420
  def set_defaults(target)
417
421
  if default_proc
418
422
  target.default_proc = default_proc.dup
@@ -14,15 +14,18 @@ module I18n
14
14
 
15
15
  config.eager_load_namespaces << I18n
16
16
 
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|
17
+ # Make sure i18n is ready before eager loading, in case any eager loaded
18
+ # code needs it.
19
+ config.before_eager_load do |app|
20
20
  I18n::Railtie.initialize_i18n(app)
21
21
  end
22
22
 
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|
23
+ # i18n initialization needs to run after application initialization, since
24
+ # initializers may configure i18n.
25
+ #
26
+ # If the application eager loaded, this was done on before_eager_load. The
27
+ # hook is still OK, though, because initialize_i18n is idempotent.
28
+ config.after_initialize do |app|
26
29
  I18n::Railtie.initialize_i18n(app)
27
30
  end
28
31
 
@@ -49,7 +52,8 @@ module I18n
49
52
  when :load_path
50
53
  I18n.load_path += value
51
54
  when :raise_on_missing_translations
52
- setup_raise_on_missing_translations_config(app)
55
+ strict = value == :strict
56
+ setup_raise_on_missing_translations_config(app, strict)
53
57
  else
54
58
  I18n.public_send("#{setting}=", value)
55
59
  end
@@ -62,8 +66,9 @@ module I18n
62
66
 
63
67
  if app.config.reloading_enabled?
64
68
  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) }
69
+ root_load_paths = I18n.load_path.select { |path| path.start_with?(Rails.root.to_s) }
70
+ reloader = app.config.file_watcher.new(root_load_paths, directories) do
71
+ I18n.load_path.delete_if { |p| p.start_with?(Rails.root.to_s) && !File.exist?(p) }
67
72
  I18n.load_path |= reloadable_paths.flat_map(&:existent)
68
73
  end
69
74
 
@@ -77,11 +82,15 @@ module I18n
77
82
  @i18n_inited = true
78
83
  end
79
84
 
80
- def self.setup_raise_on_missing_translations_config(app)
85
+ def self.setup_raise_on_missing_translations_config(app, strict)
81
86
  ActiveSupport.on_load(:action_view) do
82
87
  ActionView::Helpers::TranslationHelper.raise_on_missing_translations = app.config.i18n.raise_on_missing_translations
83
88
  end
84
89
 
90
+ ActiveSupport.on_load(:active_model_translation) do
91
+ ActiveModel::Translation.raise_on_missing_translations = app.config.i18n.raise_on_missing_translations if strict
92
+ end
93
+
85
94
  if app.config.i18n.raise_on_missing_translations &&
86
95
  I18n.exception_handler.is_a?(I18n::ExceptionHandler) # Only override the i18n gem's default exception handler.
87
96
 
@@ -1,6 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "fiber"
4
3
 
5
4
  module ActiveSupport
6
5
  module IsolatedExecutionState # :nodoc:
@@ -36,14 +36,14 @@ module ActiveSupport
36
36
  # Encode the given object into a JSON string
37
37
  def encode(value)
38
38
  unless options.empty?
39
- value = value.as_json(options.dup)
39
+ value = value.as_json(options.dup.freeze)
40
40
  end
41
41
  json = stringify(jsonify(value))
42
42
 
43
43
  # Rails does more escaping than the JSON gem natively does (we
44
44
  # escape \u2028 and \u2029 and optionally >, <, & to work around
45
45
  # certain browser problems).
46
- if Encoding.escape_html_entities_in_json
46
+ if @options.fetch(:escape_html_entities, Encoding.escape_html_entities_in_json)
47
47
  json.gsub!(">", '\u003e')
48
48
  json.gsub!("<", '\u003c')
49
49
  json.gsub!("&", '\u0026')
@@ -36,8 +36,8 @@ module ActiveSupport
36
36
  # event.allocations # => 1826 (objects)
37
37
  # end
38
38
  #
39
- # +Event+ objects record CPU time and allocations. If you don't need this
40
- # it's also possible to pass a block that accepts five arguments:
39
+ # +Event+ objects record CPU time and allocations. If you don't need this
40
+ # it's also possible to pass a block that accepts five arguments:
41
41
  #
42
42
  # ActiveSupport::Notifications.subscribe('render') do |name, start, finish, id, payload|
43
43
  # name # => String, name of the event (such as 'render' from above)
@@ -1,6 +1,28 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActiveSupport
4
+ # = Number Helper
5
+ #
6
+ # Provides methods for formatting numbers into currencies, percentages,
7
+ # phone numbers, and more.
8
+ #
9
+ # Example usage in a class:
10
+ # class Topic
11
+ # include ActiveSupport::NumberHelper
12
+ #
13
+ # def price
14
+ # number_to_currency(@price)
15
+ # end
16
+ # end
17
+ #
18
+ # Example usage in a module:
19
+ # require "active_support/number_helper"
20
+ #
21
+ # module NumberFormatting
22
+ # def format_price(price)
23
+ # ActiveSupport::NumberHelper.number_to_currency(price)
24
+ # end
25
+ # end
4
26
  module NumberHelper
5
27
  extend ActiveSupport::Autoload
6
28
 
@@ -96,6 +96,10 @@ module ActiveSupport
96
96
  config.eager_load_namespaces << TZInfo
97
97
  end
98
98
 
99
+ initializer "active_support.to_time_preserves_timezone" do |app|
100
+ ActiveSupport.to_time_preserves_timezone = app.config.active_support.to_time_preserves_timezone
101
+ end
102
+
99
103
  # Sets the default week start
100
104
  # If assigned value is not a valid day symbol (e.g. :sunday, :monday, ...), an exception will be raised.
101
105
  initializer "active_support.initialize_beginning_of_week" do |app|
@@ -67,6 +67,7 @@ module ActiveSupport
67
67
 
68
68
  # Adds event subscribers for all new methods added to the class.
69
69
  def method_added(event)
70
+ super
70
71
  # Only public methods are added as subscribers, and only if a notifier
71
72
  # has been set up. This means that subscribers will only be set up for
72
73
  # classes that call #attach_to.