activesupport 7.2.3 → 8.0.5

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 (68) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +191 -213
  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/cache/file_store.rb +12 -2
  7. data/lib/active_support/cache/mem_cache_store.rb +4 -1
  8. data/lib/active_support/cache/memory_store.rb +6 -2
  9. data/lib/active_support/cache/redis_cache_store.rb +6 -3
  10. data/lib/active_support/cache.rb +16 -11
  11. data/lib/active_support/callbacks.rb +5 -3
  12. data/lib/active_support/class_attribute.rb +33 -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/array/conversions.rb +3 -3
  17. data/lib/active_support/core_ext/benchmark.rb +6 -9
  18. data/lib/active_support/core_ext/class/attribute.rb +24 -18
  19. data/lib/active_support/core_ext/date/conversions.rb +2 -0
  20. data/lib/active_support/core_ext/date_and_time/compatibility.rb +2 -2
  21. data/lib/active_support/core_ext/enumerable.rb +8 -3
  22. data/lib/active_support/core_ext/hash/deep_merge.rb +1 -0
  23. data/lib/active_support/core_ext/hash/except.rb +0 -12
  24. data/lib/active_support/core_ext/module/attr_internal.rb +3 -4
  25. data/lib/active_support/core_ext/object/json.rb +16 -10
  26. data/lib/active_support/core_ext/object/to_query.rb +2 -1
  27. data/lib/active_support/core_ext/range/overlap.rb +3 -3
  28. data/lib/active_support/core_ext/string/inflections.rb +1 -1
  29. data/lib/active_support/core_ext/string/output_safety.rb +3 -1
  30. data/lib/active_support/core_ext/thread/backtrace/location.rb +2 -7
  31. data/lib/active_support/core_ext/time/calculations.rb +14 -2
  32. data/lib/active_support/core_ext/time/conversions.rb +2 -0
  33. data/lib/active_support/delegation.rb +25 -44
  34. data/lib/active_support/dependencies.rb +0 -1
  35. data/lib/active_support/deprecation/reporting.rb +0 -19
  36. data/lib/active_support/deprecation.rb +1 -1
  37. data/lib/active_support/duration.rb +14 -10
  38. data/lib/active_support/encrypted_configuration.rb +20 -2
  39. data/lib/active_support/error_reporter.rb +31 -1
  40. data/lib/active_support/evented_file_update_checker.rb +0 -1
  41. data/lib/active_support/gem_version.rb +3 -3
  42. data/lib/active_support/hash_with_indifferent_access.rb +15 -16
  43. data/lib/active_support/i18n_railtie.rb +19 -11
  44. data/lib/active_support/inflector/inflections.rb +2 -1
  45. data/lib/active_support/inflector/methods.rb +3 -3
  46. data/lib/active_support/isolated_execution_state.rb +4 -4
  47. data/lib/active_support/json/decoding.rb +3 -1
  48. data/lib/active_support/json/encoding.rb +2 -2
  49. data/lib/active_support/logger_thread_safe_level.rb +6 -3
  50. data/lib/active_support/message_pack/extensions.rb +1 -1
  51. data/lib/active_support/notifications/fanout.rb +0 -1
  52. data/lib/active_support/notifications/instrumenter.rb +1 -1
  53. data/lib/active_support/number_helper/number_converter.rb +1 -1
  54. data/lib/active_support/number_helper/number_to_delimited_converter.rb +17 -2
  55. data/lib/active_support/number_helper.rb +22 -0
  56. data/lib/active_support/railtie.rb +6 -0
  57. data/lib/active_support/tagged_logging.rb +5 -0
  58. data/lib/active_support/test_case.rb +6 -0
  59. data/lib/active_support/testing/assertions.rb +84 -21
  60. data/lib/active_support/testing/autorun.rb +5 -0
  61. data/lib/active_support/testing/isolation.rb +0 -2
  62. data/lib/active_support/testing/parallelization/worker.rb +5 -1
  63. data/lib/active_support/testing/time_helpers.rb +2 -1
  64. data/lib/active_support/time_with_zone.rb +22 -13
  65. data/lib/active_support/values/time_zone.rb +11 -9
  66. data/lib/active_support.rb +10 -3
  67. metadata +22 -7
  68. data/lib/active_support/proxy_object.rb +0 -20
@@ -150,7 +150,7 @@ module ActiveSupport
150
150
  universal_options = redis_options.extract!(*UNIVERSAL_OPTIONS)
151
151
 
152
152
  if pool_options = self.class.send(:retrieve_pool_options, redis_options)
153
- @redis = ::ConnectionPool.new(pool_options) { self.class.build_redis(**redis_options) }
153
+ @redis = ::ConnectionPool.new(**pool_options) { self.class.build_redis(**redis_options) }
154
154
  else
155
155
  @redis = self.class.build_redis(**redis_options)
156
156
  end
@@ -173,9 +173,12 @@ module ActiveSupport
173
173
  return {} if names.empty?
174
174
 
175
175
  options = names.extract_options!
176
- instrument_multi(:read_multi, names, options) do |payload|
176
+ options = merged_options(options)
177
+ keys = names.map { |name| normalize_key(name, options) }
178
+
179
+ instrument_multi(:read_multi, keys, options) do |payload|
177
180
  read_multi_entries(names, **options).tap do |results|
178
- payload[:hits] = results.keys
181
+ payload[:hits] = results.keys.map { |name| normalize_key(name, options) }
179
182
  end
180
183
  end
181
184
  end
@@ -287,7 +287,7 @@ module ActiveSupport
287
287
  # <tt>coder: nil</tt> to avoid the overhead of safeguarding against
288
288
  # mutation.
289
289
  #
290
- # The +:coder+ option is mutally exclusive with the +:serializer+ and
290
+ # The +:coder+ option is mutually exclusive with the +:serializer+ and
291
291
  # +:compressor+ options. Specifying them together will raise an
292
292
  # +ArgumentError+.
293
293
  #
@@ -420,7 +420,7 @@ module ActiveSupport
420
420
  # t1.join
421
421
  #
422
422
  # p val_1 # => "new value 1"
423
- # p val_2 # => "oritinal value"
423
+ # p val_2 # => "original value"
424
424
  # p cache.fetch("foo") # => "new value 1"
425
425
  #
426
426
  # # The entry requires 3 seconds to expire (expires_in + race_condition_ttl)
@@ -539,10 +539,11 @@ module ActiveSupport
539
539
 
540
540
  options = names.extract_options!
541
541
  options = merged_options(options)
542
+ keys = names.map { |name| normalize_key(name, options) }
542
543
 
543
- instrument_multi :read_multi, names, options do |payload|
544
+ instrument_multi :read_multi, keys, options do |payload|
544
545
  read_multi_entries(names, **options, event: payload).tap do |results|
545
- payload[:hits] = results.keys
546
+ payload[:hits] = results.keys.map { |name| normalize_key(name, options) }
546
547
  end
547
548
  end
548
549
  end
@@ -552,8 +553,9 @@ module ActiveSupport
552
553
  return hash if hash.empty?
553
554
 
554
555
  options = merged_options(options)
556
+ normalized_hash = hash.transform_keys { |key| normalize_key(key, options) }
555
557
 
556
- instrument_multi :write_multi, hash, options do |payload|
558
+ instrument_multi :write_multi, normalized_hash, options do |payload|
557
559
  entries = hash.each_with_object({}) do |(name, value), memo|
558
560
  memo[normalize_key(name, options)] = Entry.new(value, **options.merge(version: normalize_version(name, options)))
559
561
  end
@@ -597,9 +599,9 @@ module ActiveSupport
597
599
 
598
600
  options = names.extract_options!
599
601
  options = merged_options(options)
600
-
602
+ keys = names.map { |name| normalize_key(name, options) }
601
603
  writes = {}
602
- ordered = instrument_multi :read_multi, names, options do |payload|
604
+ ordered = instrument_multi :read_multi, keys, options do |payload|
603
605
  if options[:force]
604
606
  reads = {}
605
607
  else
@@ -611,7 +613,7 @@ module ActiveSupport
611
613
  end
612
614
  writes.compact! if options[:skip_nil]
613
615
 
614
- payload[:hits] = reads.keys
616
+ payload[:hits] = reads.keys.map { |name| normalize_key(name, options) }
615
617
  payload[:super_operation] = :fetch_multi
616
618
 
617
619
  ordered
@@ -944,9 +946,12 @@ module ActiveSupport
944
946
  #
945
947
  # namespace_key 'foo', namespace: -> { 'cache' }
946
948
  # # => 'cache:foo'
947
- def namespace_key(key, options = nil)
948
- options = merged_options(options)
949
- namespace = options[:namespace]
949
+ def namespace_key(key, call_options = nil)
950
+ namespace = if call_options&.key?(:namespace)
951
+ call_options[:namespace]
952
+ else
953
+ options[:namespace]
954
+ end
950
955
 
951
956
  if namespace.respond_to?(:call)
952
957
  namespace = namespace.call
@@ -6,7 +6,6 @@ require "active_support/core_ext/array/extract_options"
6
6
  require "active_support/core_ext/class/attribute"
7
7
  require "active_support/core_ext/string/filters"
8
8
  require "active_support/core_ext/object/blank"
9
- require "thread"
10
9
 
11
10
  module ActiveSupport
12
11
  # = Active Support \Callbacks
@@ -67,7 +66,7 @@ module ActiveSupport
67
66
 
68
67
  included do
69
68
  extend ActiveSupport::DescendantsTracker
70
- class_attribute :__callbacks, instance_writer: false, default: {}
69
+ class_attribute :__callbacks, instance_writer: false, instance_predicate: false, default: {}
71
70
  end
72
71
 
73
72
  CALLBACK_FILTER_TYPES = [:before, :after, :around].freeze
@@ -935,7 +934,10 @@ module ActiveSupport
935
934
  end
936
935
 
937
936
  def set_callbacks(name, callbacks) # :nodoc:
938
- unless singleton_class.method_defined?(:__callbacks, false)
937
+ # HACK: We're making assumption on how `class_attribute` is implemented
938
+ # to save constantly duping the callback hash. If this desync with class_attribute
939
+ # we'll lose the optimization, but won't cause an actual behavior bug.
940
+ unless singleton_class.private_method_defined?(:__class_attr__callbacks, false)
939
941
  self.__callbacks = __callbacks.dup
940
942
  end
941
943
  self.__callbacks[name.to_sym] = callbacks
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveSupport
4
+ module ClassAttribute # :nodoc:
5
+ class << self
6
+ def redefine(owner, name, namespaced_name, value)
7
+ if owner.singleton_class?
8
+ if owner.attached_object.is_a?(Module)
9
+ redefine_method(owner, namespaced_name, private: true) { value }
10
+ else
11
+ redefine_method(owner, name) { value }
12
+ end
13
+ end
14
+
15
+ redefine_method(owner.singleton_class, namespaced_name, private: true) { value }
16
+
17
+ redefine_method(owner.singleton_class, "#{namespaced_name}=", private: true) do |new_value|
18
+ if owner.equal?(self)
19
+ value = new_value
20
+ else
21
+ ::ActiveSupport::ClassAttribute.redefine(self, name, namespaced_name, new_value)
22
+ end
23
+ end
24
+ end
25
+
26
+ def redefine_method(owner, name, private: false, &block)
27
+ owner.silence_redefinition_of_method(name)
28
+ owner.define_method(name, &block)
29
+ owner.send(:private, name) if private
30
+ end
31
+ end
32
+ end
33
+ end
@@ -55,6 +55,11 @@ module ActiveSupport
55
55
  @path = path
56
56
  @line = line
57
57
  @namespaces = Hash.new { |h, k| h[k] = MethodSet.new(k) }
58
+ @sources = []
59
+ end
60
+
61
+ def class_eval
62
+ yield @sources
58
63
  end
59
64
 
60
65
  def define_cached_method(canonical_name, namespace:, as: nil, &block)
@@ -65,6 +70,10 @@ module ActiveSupport
65
70
  @namespaces.each_value do |method_set|
66
71
  method_set.apply(@owner, @path, @line - 1)
67
72
  end
73
+
74
+ unless @sources.empty?
75
+ @owner.class_eval("# frozen_string_literal: true\n" + @sources.join(";"), @path, @line - 1)
76
+ end
68
77
  end
69
78
  end
70
79
  end
@@ -1,6 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "thread"
4
3
  require "monitor"
5
4
 
6
5
  module ActiveSupport
@@ -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
@@ -16,11 +16,11 @@ class Array
16
16
  # ==== Options
17
17
  #
18
18
  # * <tt>:words_connector</tt> - The sign or word used to join all but the last
19
- # element in arrays with three or more elements (default: ", ").
19
+ # element in arrays with three or more elements (default: <tt>", "</tt>).
20
20
  # * <tt>:last_word_connector</tt> - The sign or word used to join the last element
21
- # in arrays with three or more elements (default: ", and ").
21
+ # in arrays with three or more elements (default: <tt>", and "</tt>).
22
22
  # * <tt>:two_words_connector</tt> - The sign or word used to join the elements
23
- # in arrays with two elements (default: " and ").
23
+ # in arrays with two elements (default: <tt>" and "</tt>).
24
24
  # * <tt>:locale</tt> - If +i18n+ is available, you can set a locale and use
25
25
  # the connector options defined on the 'support.array' namespace in the
26
26
  # corresponding dictionary file.
@@ -4,14 +4,11 @@ require "benchmark"
4
4
  return if Benchmark.respond_to?(:ms)
5
5
 
6
6
  class << Benchmark
7
- # Benchmark realtime in milliseconds.
8
- #
9
- # Benchmark.realtime { User.all }
10
- # # => 8.0e-05
11
- #
12
- # Benchmark.ms { User.all }
13
- # # => 0.074
14
- def ms(&block)
15
- 1000 * realtime(&block)
7
+ def ms(&block) # :nodoc
8
+ # NOTE: Please also remove the Active Support `benchmark` dependency when removing this
9
+ ActiveSupport.deprecator.warn <<~TEXT
10
+ `Benchmark.ms` is deprecated and will be removed in Rails 8.1 without replacement.
11
+ TEXT
12
+ ActiveSupport::Benchmark.realtime(:float_millisecond, &block)
16
13
  end
17
14
  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,31 @@ 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
95
+ name = name.to_sym
96
+ namespaced_name = :"__class_attr_#{name}"
97
+ ::ActiveSupport::ClassAttribute.redefine(self, name, namespaced_name, default)
98
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
99
+ class_methods << "def #{name}; #{namespaced_name}; end"
100
+ class_methods << "def #{name}=(value); self.#{namespaced_name} = value; end"
104
101
 
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
102
+ if singleton_class?
103
+ methods << <<~RUBY if instance_reader
104
+ silence_redefinition_of_method(:#{name})
105
+ def #{name}
106
+ self.singleton_class.#{name}
107
+ end
108
+ RUBY
109
+ else
110
+ methods << <<~RUBY if instance_reader
111
+ silence_redefinition_of_method def #{name}
112
+ if defined?(@#{name})
113
+ @#{name}
114
+ else
115
+ self.class.#{name}
116
+ end
117
+ end
118
+ RUBY
119
+ end
112
120
 
113
121
  methods << <<~RUBY if instance_writer
114
122
  silence_redefinition_of_method(:#{name}=)
@@ -125,7 +133,5 @@ class Class
125
133
 
126
134
  location = caller_locations(1, 1).first
127
135
  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
136
  end
131
137
  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.1." \
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
@@ -36,6 +36,7 @@ class Hash
36
36
  #--
37
37
  # Implemented by ActiveSupport::DeepMergeable#deep_merge!.
38
38
 
39
+ ##
39
40
  def deep_merge?(other) # :nodoc:
40
41
  other.is_a?(Hash)
41
42
  end
@@ -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 }
@@ -24,14 +24,13 @@ class Module
24
24
 
25
25
  def attr_internal_naming_format=(format)
26
26
  if format.start_with?("@")
27
- ActiveSupport.deprecator.warn <<~MESSAGE
28
- Setting `attr_internal_naming_format` with a `@` prefix is deprecated and will be removed in Rails 8.0.
27
+ raise ArgumentError, <<~MESSAGE.squish
28
+ Setting `attr_internal_naming_format` with a `@` prefix is not supported.
29
29
 
30
30
  You can simply replace #{format.inspect} by #{format.delete_prefix("@").inspect}.
31
31
  MESSAGE
32
-
33
- format = format.delete_prefix("@")
34
32
  end
33
+
35
34
  @attr_internal_naming_format = format
36
35
  end
37
36
  end
@@ -4,7 +4,7 @@
4
4
  require "json"
5
5
  require "bigdecimal"
6
6
  require "ipaddr"
7
- require "uri/generic"
7
+ require "uri"
8
8
  require "pathname"
9
9
  require "active_support/core_ext/big_decimal/conversions" # for #to_s
10
10
  require "active_support/core_ext/hash/except"
@@ -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
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "cgi"
3
+ require "cgi/escape"
4
+ require "cgi/util" if RUBY_VERSION < "3.5"
4
5
 
5
6
  class Object
6
7
  # Alias of <tt>to_s</tt>.
@@ -1,10 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class Range
4
- # Compare two ranges and see if they overlap each other
5
- # (1..5).overlap?(4..6) # => true
6
- # (1..5).overlap?(7..9) # => false
7
4
  unless Range.method_defined?(:overlap?) # Ruby 3.3+
5
+ # Compare two ranges and see if they overlap each other
6
+ # (1..5).overlap?(4..6) # => true
7
+ # (1..5).overlap?(7..9) # => false
8
8
  def overlap?(other)
9
9
  raise TypeError unless other.is_a? Range
10
10
 
@@ -248,7 +248,7 @@ class String
248
248
  # optional parameter +capitalize+ to false.
249
249
  # By default, this parameter is true.
250
250
  #
251
- # The trailing '_id' can be kept and capitalized by setting the
251
+ # The trailing '_id' can be kept by setting the
252
252
  # optional parameter +keep_id_suffix+ to true.
253
253
  # By default, this parameter is false.
254
254
  #
@@ -128,7 +128,9 @@ module ActiveSupport # :nodoc:
128
128
  escaped_args = Array(args).map { |arg| explicit_html_escape_interpolated_argument(arg) }
129
129
  end
130
130
 
131
- self.class.new(super(escaped_args))
131
+ new_safe_buffer = self.class.new(super(escaped_args))
132
+ new_safe_buffer.instance_variable_set(:@html_safe, @html_safe)
133
+ new_safe_buffer
132
134
  end
133
135
 
134
136
  attr_reader :html_safe
@@ -1,12 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class Thread::Backtrace::Location # :nodoc:
4
- if defined?(ErrorHighlight) && Gem::Version.new(ErrorHighlight::VERSION) >= Gem::Version.new("0.4.0")
5
- def spot(ex)
6
- ErrorHighlight.spot(ex, backtrace_location: self)
7
- end
8
- else
9
- def spot(ex)
10
- end
4
+ def spot(ex)
5
+ ErrorHighlight.spot(ex, backtrace_location: self)
11
6
  end
12
7
  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+