activesupport 7.2.2.2 → 8.0.3

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 (81) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +182 -162
  3. data/README.rdoc +1 -1
  4. data/lib/active_support/backtrace_cleaner.rb +2 -2
  5. data/lib/active_support/benchmark.rb +21 -0
  6. data/lib/active_support/benchmarkable.rb +3 -2
  7. data/lib/active_support/broadcast_logger.rb +61 -74
  8. data/lib/active_support/cache/file_store.rb +14 -4
  9. data/lib/active_support/cache/mem_cache_store.rb +13 -15
  10. data/lib/active_support/cache/memory_store.rb +9 -5
  11. data/lib/active_support/cache/null_store.rb +2 -2
  12. data/lib/active_support/cache/redis_cache_store.rb +6 -3
  13. data/lib/active_support/cache/strategy/local_cache.rb +56 -20
  14. data/lib/active_support/cache.rb +19 -14
  15. data/lib/active_support/callbacks.rb +8 -5
  16. data/lib/active_support/class_attribute.rb +33 -0
  17. data/lib/active_support/code_generator.rb +9 -0
  18. data/lib/active_support/concurrency/share_lock.rb +0 -1
  19. data/lib/active_support/configuration_file.rb +15 -6
  20. data/lib/active_support/core_ext/array/conversions.rb +3 -3
  21. data/lib/active_support/core_ext/benchmark.rb +7 -9
  22. data/lib/active_support/core_ext/class/attribute.rb +24 -20
  23. data/lib/active_support/core_ext/date/conversions.rb +2 -0
  24. data/lib/active_support/core_ext/date_and_time/compatibility.rb +2 -2
  25. data/lib/active_support/core_ext/date_time/conversions.rb +4 -2
  26. data/lib/active_support/core_ext/enumerable.rb +25 -8
  27. data/lib/active_support/core_ext/erb/util.rb +2 -2
  28. data/lib/active_support/core_ext/hash/deep_merge.rb +1 -0
  29. data/lib/active_support/core_ext/hash/except.rb +0 -12
  30. data/lib/active_support/core_ext/module/attr_internal.rb +3 -4
  31. data/lib/active_support/core_ext/module/introspection.rb +3 -0
  32. data/lib/active_support/core_ext/object/json.rb +15 -9
  33. data/lib/active_support/core_ext/object/to_query.rb +2 -1
  34. data/lib/active_support/core_ext/object/try.rb +2 -2
  35. data/lib/active_support/core_ext/range/overlap.rb +3 -3
  36. data/lib/active_support/core_ext/range/sole.rb +17 -0
  37. data/lib/active_support/core_ext/range.rb +1 -0
  38. data/lib/active_support/core_ext/securerandom.rb +24 -8
  39. data/lib/active_support/core_ext/string/filters.rb +3 -3
  40. data/lib/active_support/core_ext/string/multibyte.rb +2 -2
  41. data/lib/active_support/core_ext/thread/backtrace/location.rb +2 -7
  42. data/lib/active_support/core_ext/time/calculations.rb +14 -2
  43. data/lib/active_support/core_ext/time/compatibility.rb +9 -1
  44. data/lib/active_support/core_ext/time/conversions.rb +2 -0
  45. data/lib/active_support/current_attributes.rb +14 -7
  46. data/lib/active_support/delegation.rb +0 -2
  47. data/lib/active_support/dependencies.rb +0 -1
  48. data/lib/active_support/deprecation/reporting.rb +0 -19
  49. data/lib/active_support/deprecation.rb +1 -1
  50. data/lib/active_support/duration.rb +14 -10
  51. data/lib/active_support/encrypted_configuration.rb +20 -2
  52. data/lib/active_support/error_reporter.rb +36 -3
  53. data/lib/active_support/evented_file_update_checker.rb +0 -1
  54. data/lib/active_support/execution_wrapper.rb +1 -1
  55. data/lib/active_support/file_update_checker.rb +1 -1
  56. data/lib/active_support/gem_version.rb +4 -4
  57. data/lib/active_support/hash_with_indifferent_access.rb +34 -31
  58. data/lib/active_support/i18n_railtie.rb +19 -11
  59. data/lib/active_support/isolated_execution_state.rb +0 -2
  60. data/lib/active_support/json/decoding.rb +4 -2
  61. data/lib/active_support/json/encoding.rb +25 -7
  62. data/lib/active_support/lazy_load_hooks.rb +1 -1
  63. data/lib/active_support/logger_thread_safe_level.rb +6 -3
  64. data/lib/active_support/message_encryptors.rb +2 -2
  65. data/lib/active_support/message_verifier.rb +9 -0
  66. data/lib/active_support/message_verifiers.rb +5 -3
  67. data/lib/active_support/messages/rotator.rb +5 -0
  68. data/lib/active_support/multibyte/chars.rb +4 -1
  69. data/lib/active_support/notifications/fanout.rb +0 -1
  70. data/lib/active_support/number_helper.rb +22 -0
  71. data/lib/active_support/railtie.rb +6 -0
  72. data/lib/active_support/tagged_logging.rb +5 -0
  73. data/lib/active_support/testing/assertions.rb +79 -21
  74. data/lib/active_support/testing/isolation.rb +0 -2
  75. data/lib/active_support/testing/time_helpers.rb +2 -1
  76. data/lib/active_support/time_with_zone.rb +22 -13
  77. data/lib/active_support/values/time_zone.rb +11 -9
  78. data/lib/active_support.rb +10 -3
  79. metadata +22 -7
  80. data/lib/active_support/proxy_object.rb +0 -20
  81. data/lib/active_support/testing/strict_warnings.rb +0 -43
@@ -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.
@@ -1,16 +1,14 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "benchmark"
4
+ return if Benchmark.respond_to?(:ms)
4
5
 
5
6
  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)
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)
15
13
  end
16
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.
@@ -83,32 +84,37 @@ class Class
83
84
  #
84
85
  # class_attribute :settings, default: {}
85
86
  def class_attribute(*attrs, instance_accessor: true,
86
- instance_reader: instance_accessor, instance_writer: instance_accessor, instance_predicate: true, default: nil)
87
-
87
+ instance_reader: instance_accessor, instance_writer: instance_accessor, instance_predicate: true, default: nil
88
+ )
88
89
  class_methods, methods = [], []
89
90
  attrs.each do |name|
90
91
  unless name.is_a?(Symbol) || name.is_a?(String)
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
+ delegators = [
100
+ "def #{name}; #{namespaced_name}; end",
101
+ "def #{name}=(value); self.#{namespaced_name} = value; end",
102
+ ]
104
103
 
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
104
+ class_methods.concat(delegators)
105
+ if singleton_class?
106
+ methods.concat(delegators)
107
+ else
108
+ methods << <<~RUBY if instance_reader
109
+ silence_redefinition_of_method def #{name}
110
+ if defined?(@#{name})
111
+ @#{name}
112
+ else
113
+ self.class.#{name}
114
+ end
115
+ end
116
+ RUBY
117
+ end
112
118
 
113
119
  methods << <<~RUBY if instance_writer
114
120
  silence_redefinition_of_method(:#{name}=)
@@ -125,7 +131,5 @@ class Class
125
131
 
126
132
  location = caller_locations(1, 1).first
127
133
  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
134
  end
131
135
  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
@@ -11,7 +11,8 @@ class DateTime
11
11
  #
12
12
  # This method is aliased to <tt>to_formatted_s</tt>.
13
13
  #
14
- # === Examples
14
+ # ==== Examples
15
+ #
15
16
  # datetime = DateTime.civil(2007, 12, 4, 0, 0, 0, 0) # => Tue, 04 Dec 2007 00:00:00 +0000
16
17
  #
17
18
  # datetime.to_fs(:db) # => "2007-12-04 00:00:00"
@@ -23,7 +24,8 @@ class DateTime
23
24
  # datetime.to_fs(:rfc822) # => "Tue, 04 Dec 2007 00:00:00 +0000"
24
25
  # datetime.to_fs(:iso8601) # => "2007-12-04T00:00:00+00:00"
25
26
  #
26
- # == Adding your own datetime formats to to_fs
27
+ # ==== Adding your own datetime formats to +to_fs+
28
+ #
27
29
  # DateTime formats are shared with Time. You can add your own to the
28
30
  # Time::DATE_FORMATS hash. Use the format name as the hash key and
29
31
  # either a strftime string or Proc instance that takes a time or
@@ -192,22 +192,39 @@ 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
201
- # than one item, raises +Enumerable::SoleItemExpectedError+.
206
+ # than one item, raises Enumerable::SoleItemExpectedError.
202
207
  #
203
208
  # ["x"].sole # => "x"
204
209
  # Set.new.sole # => Enumerable::SoleItemExpectedError: no item found
205
210
  # { a: 1, b: 2 }.sole # => Enumerable::SoleItemExpectedError: multiple items found
206
211
  def sole
207
- case count
208
- when 1 then return first # rubocop:disable Style/RedundantReturn
209
- when 0 then raise ActiveSupport::EnumerableCoreExt::SoleItemExpectedError, "no item found"
210
- when 2.. then raise ActiveSupport::EnumerableCoreExt::SoleItemExpectedError, "multiple items found"
212
+ result = nil
213
+ found = false
214
+
215
+ each do |element|
216
+ if found
217
+ raise SoleItemExpectedError, "multiple items found"
218
+ end
219
+
220
+ result = element
221
+ found = true
222
+ end
223
+
224
+ if found
225
+ result
226
+ else
227
+ raise SoleItemExpectedError, "no item found"
211
228
  end
212
229
  end
213
230
  end
@@ -174,7 +174,7 @@ class ERB
174
174
 
175
175
  case source.matched
176
176
  when start_re
177
- tokens << [:TEXT, source.string[pos, len]] if len > 0
177
+ tokens << [:TEXT, source.string.byteslice(pos, len)] if len > 0
178
178
  tokens << [:OPEN, source.matched]
179
179
  if source.scan(/(.*?)(?=#{finish_re}|\z)/m)
180
180
  tokens << [:CODE, source.matched] unless source.matched.empty?
@@ -183,7 +183,7 @@ class ERB
183
183
  raise NotImplementedError
184
184
  end
185
185
  when finish_re
186
- tokens << [:CODE, source.string[pos, len]] if len > 0
186
+ tokens << [:CODE, source.string.byteslice(pos, len)] if len > 0
187
187
  tokens << [:CLOSE, source.matched]
188
188
  else
189
189
  raise NotImplementedError, source.matched
@@ -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
@@ -10,6 +10,9 @@ class Module
10
10
  if defined?(@parent_name)
11
11
  @parent_name
12
12
  else
13
+ name = self.name
14
+ return if name.nil?
15
+
13
16
  parent_name = name =~ /::[^:]+\z/ ? -$` : nil
14
17
  @parent_name = parent_name unless frozen?
15
18
  parent_name
@@ -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>.
@@ -145,14 +145,14 @@ class NilClass
145
145
  #
146
146
  # With +try+
147
147
  # @person.try(:children).try(:first).try(:name)
148
- def try(*)
148
+ def try(*, &)
149
149
  nil
150
150
  end
151
151
 
152
152
  # Calling +try!+ on +nil+ always returns +nil+.
153
153
  #
154
154
  # nil.try!(:name) # => nil
155
- def try!(*)
155
+ def try!(*, &)
156
156
  nil
157
157
  end
158
158
  end
@@ -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
 
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Range
4
+ # Returns the sole item in the range. If there are no items, or more
5
+ # than one item, raises Enumerable::SoleItemExpectedError.
6
+ #
7
+ # (1..1).sole # => 1
8
+ # (2..1).sole # => Enumerable::SoleItemExpectedError: no item found
9
+ # (..1).sole # => Enumerable::SoleItemExpectedError: infinite range cannot represent a sole item
10
+ def sole
11
+ if self.begin.nil? || self.end.nil?
12
+ raise ActiveSupport::EnumerableCoreExt::SoleItemExpectedError, "infinite range '#{inspect}' cannot represent a sole item"
13
+ end
14
+
15
+ super
16
+ end
17
+ end
@@ -4,3 +4,4 @@ require "active_support/core_ext/range/conversions"
4
4
  require "active_support/core_ext/range/compare_range"
5
5
  require "active_support/core_ext/range/overlap"
6
6
  require "active_support/core_ext/range/each"
7
+ require "active_support/core_ext/range/sole"
@@ -16,8 +16,18 @@ module SecureRandom
16
16
  #
17
17
  # p SecureRandom.base58 # => "4kUgL2pdQMSCQtjE"
18
18
  # p SecureRandom.base58(24) # => "77TMHrHJFvFDwodq8w7Ev2m7"
19
- def self.base58(n = 16)
20
- SecureRandom.alphanumeric(n, chars: BASE58_ALPHABET)
19
+ if SecureRandom.method(:alphanumeric).parameters.size == 2 # Remove check when Ruby 3.3 is the minimum supported version
20
+ def self.base58(n = 16)
21
+ alphanumeric(n, chars: BASE58_ALPHABET)
22
+ end
23
+ else
24
+ def self.base58(n = 16)
25
+ SecureRandom.random_bytes(n).unpack("C*").map do |byte|
26
+ idx = byte % 64
27
+ idx = SecureRandom.random_number(58) if idx >= 58
28
+ BASE58_ALPHABET[idx]
29
+ end.join
30
+ end
21
31
  end
22
32
 
23
33
  # SecureRandom.base36 generates a random base36 string in lowercase.
@@ -31,11 +41,17 @@ module SecureRandom
31
41
  #
32
42
  # p SecureRandom.base36 # => "4kugl2pdqmscqtje"
33
43
  # p SecureRandom.base36(24) # => "77tmhrhjfvfdwodq8w7ev2m7"
34
- def self.base36(n = 16)
35
- SecureRandom.random_bytes(n).unpack("C*").map do |byte|
36
- idx = byte % 64
37
- idx = SecureRandom.random_number(36) if idx >= 36
38
- BASE36_ALPHABET[idx]
39
- end.join
44
+ if SecureRandom.method(:alphanumeric).parameters.size == 2 # Remove check when Ruby 3.3 is the minimum supported version
45
+ def self.base36(n = 16)
46
+ alphanumeric(n, chars: BASE36_ALPHABET)
47
+ end
48
+ else
49
+ def self.base36(n = 16)
50
+ SecureRandom.random_bytes(n).unpack("C*").map do |byte|
51
+ idx = byte % 64
52
+ idx = SecureRandom.random_number(36) if idx >= 36
53
+ BASE36_ALPHABET[idx]
54
+ end.join
55
+ end
40
56
  end
41
57
  end
@@ -88,11 +88,11 @@ class String
88
88
  # characters.
89
89
  #
90
90
  # >> "🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪".size
91
- # => 20
91
+ # # => 20
92
92
  # >> "🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪".bytesize
93
- # => 80
93
+ # # => 80
94
94
  # >> "🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪".truncate_bytes(20)
95
- # => "🔪🔪🔪🔪…"
95
+ # # => "🔪🔪🔪🔪…"
96
96
  #
97
97
  # The truncated text ends with the <tt>:omission</tt> string, defaulting
98
98
  # to "…", for a total length not exceeding <tt>truncate_to</tt>.
@@ -12,12 +12,12 @@ class String
12
12
  # class. If the proxy class doesn't respond to a certain method, it's forwarded to the encapsulated string.
13
13
  #
14
14
  # >> "lj".mb_chars.upcase.to_s
15
- # => "LJ"
15
+ # # => "LJ"
16
16
  #
17
17
  # NOTE: Ruby 2.4 and later support native Unicode case mappings:
18
18
  #
19
19
  # >> "lj".upcase
20
- # => "LJ"
20
+ # # => "LJ"
21
21
  #
22
22
  # == \Method chaining
23
23
  #
@@ -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
 
@@ -15,10 +15,18 @@ class Time
15
15
  end
16
16
 
17
17
  def preserve_timezone # :nodoc:
18
- active_support_local_zone == zone || super
18
+ system_local_time? || super
19
19
  end
20
20
 
21
21
  private
22
+ def system_local_time?
23
+ if ::Time.equal?(self.class)
24
+ zone = self.zone
25
+ String === zone &&
26
+ (zone != "UTC" || active_support_local_zone == "UTC")
27
+ end
28
+ end
29
+
22
30
  @@active_support_local_tz = nil
23
31
 
24
32
  def active_support_local_zone
@@ -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+