activesupport 6.1.7.4 → 7.0.0.alpha1

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of activesupport might be problematic. Click here for more details.

Files changed (129) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +151 -632
  3. data/MIT-LICENSE +1 -1
  4. data/lib/active_support/actionable_error.rb +1 -1
  5. data/lib/active_support/array_inquirer.rb +0 -2
  6. data/lib/active_support/benchmarkable.rb +2 -2
  7. data/lib/active_support/cache/file_store.rb +15 -9
  8. data/lib/active_support/cache/mem_cache_store.rb +119 -28
  9. data/lib/active_support/cache/memory_store.rb +21 -13
  10. data/lib/active_support/cache/null_store.rb +10 -2
  11. data/lib/active_support/cache/redis_cache_store.rb +39 -59
  12. data/lib/active_support/cache/strategy/local_cache.rb +29 -49
  13. data/lib/active_support/cache.rb +189 -45
  14. data/lib/active_support/callbacks.rb +35 -31
  15. data/lib/active_support/concern.rb +5 -5
  16. data/lib/active_support/concurrency/load_interlock_aware_monitor.rb +2 -4
  17. data/lib/active_support/concurrency/share_lock.rb +2 -2
  18. data/lib/active_support/configurable.rb +6 -3
  19. data/lib/active_support/configuration_file.rb +1 -1
  20. data/lib/active_support/core_ext/array/access.rb +1 -5
  21. data/lib/active_support/core_ext/array/conversions.rb +6 -6
  22. data/lib/active_support/core_ext/array/grouping.rb +6 -6
  23. data/lib/active_support/core_ext/big_decimal/conversions.rb +1 -1
  24. data/lib/active_support/core_ext/date/blank.rb +1 -1
  25. data/lib/active_support/core_ext/date/calculations.rb +2 -2
  26. data/lib/active_support/core_ext/date_time/blank.rb +1 -1
  27. data/lib/active_support/core_ext/digest/uuid.rb +13 -13
  28. data/lib/active_support/core_ext/enumerable.rb +64 -12
  29. data/lib/active_support/core_ext/file/atomic.rb +1 -1
  30. data/lib/active_support/core_ext/hash/keys.rb +1 -1
  31. data/lib/active_support/core_ext/kernel/reporting.rb +4 -4
  32. data/lib/active_support/core_ext/module/delegation.rb +2 -8
  33. data/lib/active_support/core_ext/name_error.rb +2 -8
  34. data/lib/active_support/core_ext/numeric/conversions.rb +2 -2
  35. data/lib/active_support/core_ext/object/blank.rb +2 -2
  36. data/lib/active_support/core_ext/object/deep_dup.rb +1 -1
  37. data/lib/active_support/core_ext/object/duplicable.rb +11 -0
  38. data/lib/active_support/core_ext/object/json.rb +29 -24
  39. data/lib/active_support/core_ext/object/to_query.rb +2 -2
  40. data/lib/active_support/core_ext/object/try.rb +20 -20
  41. data/lib/active_support/core_ext/range/compare_range.rb +0 -25
  42. data/lib/active_support/core_ext/range/each.rb +1 -1
  43. data/lib/active_support/core_ext/range/include_time_with_zone.rb +1 -1
  44. data/lib/active_support/core_ext/string/filters.rb +1 -1
  45. data/lib/active_support/core_ext/string/inflections.rb +1 -1
  46. data/lib/active_support/core_ext/string/output_safety.rb +60 -68
  47. data/lib/active_support/core_ext/symbol/starts_ends_with.rb +0 -8
  48. data/lib/active_support/core_ext/time/calculations.rb +4 -5
  49. data/lib/active_support/core_ext/time/zones.rb +2 -17
  50. data/lib/active_support/core_ext/uri.rb +0 -14
  51. data/lib/active_support/current_attributes.rb +17 -2
  52. data/lib/active_support/dependencies/interlock.rb +10 -18
  53. data/lib/active_support/dependencies/require_dependency.rb +28 -0
  54. data/lib/active_support/dependencies.rb +58 -788
  55. data/lib/active_support/deprecation/behaviors.rb +4 -1
  56. data/lib/active_support/deprecation/method_wrappers.rb +3 -3
  57. data/lib/active_support/deprecation/proxy_wrappers.rb +1 -1
  58. data/lib/active_support/deprecation.rb +1 -1
  59. data/lib/active_support/descendants_tracker.rb +12 -9
  60. data/lib/active_support/digest.rb +4 -4
  61. data/lib/active_support/duration/iso8601_parser.rb +3 -3
  62. data/lib/active_support/duration/iso8601_serializer.rb +9 -1
  63. data/lib/active_support/duration.rb +80 -52
  64. data/lib/active_support/encrypted_configuration.rb +11 -1
  65. data/lib/active_support/encrypted_file.rb +1 -1
  66. data/lib/active_support/environment_inquirer.rb +1 -1
  67. data/lib/active_support/evented_file_update_checker.rb +1 -1
  68. data/lib/active_support/execution_wrapper.rb +13 -16
  69. data/lib/active_support/fork_tracker.rb +2 -4
  70. data/lib/active_support/gem_version.rb +4 -4
  71. data/lib/active_support/hash_with_indifferent_access.rb +3 -1
  72. data/lib/active_support/i18n.rb +1 -0
  73. data/lib/active_support/inflector/inflections.rb +11 -4
  74. data/lib/active_support/inflector/methods.rb +23 -46
  75. data/lib/active_support/json/encoding.rb +3 -3
  76. data/lib/active_support/key_generator.rb +18 -1
  77. data/lib/active_support/locale/en.yml +1 -1
  78. data/lib/active_support/log_subscriber.rb +13 -3
  79. data/lib/active_support/logger_thread_safe_level.rb +5 -13
  80. data/lib/active_support/message_encryptor.rb +3 -3
  81. data/lib/active_support/message_verifier.rb +4 -4
  82. data/lib/active_support/messages/metadata.rb +2 -2
  83. data/lib/active_support/multibyte/chars.rb +10 -11
  84. data/lib/active_support/multibyte.rb +1 -1
  85. data/lib/active_support/notifications/fanout.rb +31 -11
  86. data/lib/active_support/notifications/instrumenter.rb +17 -0
  87. data/lib/active_support/notifications.rb +10 -0
  88. data/lib/active_support/number_helper/number_converter.rb +1 -3
  89. data/lib/active_support/number_helper/number_to_currency_converter.rb +11 -6
  90. data/lib/active_support/number_helper/number_to_delimited_converter.rb +1 -1
  91. data/lib/active_support/number_helper/number_to_human_size_converter.rb +1 -1
  92. data/lib/active_support/number_helper/number_to_phone_converter.rb +1 -1
  93. data/lib/active_support/number_helper/rounding_helper.rb +1 -5
  94. data/lib/active_support/number_helper.rb +0 -2
  95. data/lib/active_support/option_merger.rb +4 -16
  96. data/lib/active_support/ordered_hash.rb +1 -1
  97. data/lib/active_support/parameter_filter.rb +5 -0
  98. data/lib/active_support/per_thread_registry.rb +1 -1
  99. data/lib/active_support/railtie.rb +33 -10
  100. data/lib/active_support/reloader.rb +1 -1
  101. data/lib/active_support/rescuable.rb +2 -2
  102. data/lib/active_support/secure_compare_rotator.rb +1 -1
  103. data/lib/active_support/string_inquirer.rb +0 -2
  104. data/lib/active_support/subscriber.rb +5 -0
  105. data/lib/active_support/test_case.rb +9 -21
  106. data/lib/active_support/testing/assertions.rb +34 -4
  107. data/lib/active_support/testing/deprecation.rb +1 -1
  108. data/lib/active_support/testing/isolation.rb +1 -1
  109. data/lib/active_support/testing/method_call_assertions.rb +5 -5
  110. data/lib/active_support/testing/parallelization/server.rb +4 -0
  111. data/lib/active_support/testing/parallelization/worker.rb +3 -0
  112. data/lib/active_support/testing/parallelization.rb +4 -0
  113. data/lib/active_support/testing/parallelize_executor.rb +76 -0
  114. data/lib/active_support/testing/stream.rb +3 -5
  115. data/lib/active_support/testing/tagged_logging.rb +1 -1
  116. data/lib/active_support/testing/time_helpers.rb +13 -2
  117. data/lib/active_support/time_with_zone.rb +19 -6
  118. data/lib/active_support/values/time_zone.rb +25 -11
  119. data/lib/active_support/xml_mini/jdom.rb +1 -1
  120. data/lib/active_support/xml_mini/libxml.rb +5 -5
  121. data/lib/active_support/xml_mini/libxmlsax.rb +1 -1
  122. data/lib/active_support/xml_mini/nokogiri.rb +4 -4
  123. data/lib/active_support/xml_mini/nokogirisax.rb +1 -1
  124. data/lib/active_support/xml_mini/rexml.rb +1 -1
  125. data/lib/active_support/xml_mini.rb +2 -1
  126. data/lib/active_support.rb +14 -1
  127. metadata +14 -29
  128. data/lib/active_support/core_ext/marshal.rb +0 -26
  129. data/lib/active_support/dependencies/zeitwerk_integration.rb +0 -120
@@ -17,14 +17,12 @@ module ActiveSupport
17
17
  ActiveSupport::Dependencies.interlock.permit_concurrent_loads { super }
18
18
  end
19
19
 
20
- def synchronize
20
+ def synchronize(&block)
21
21
  Thread.handle_interrupt(EXCEPTION_NEVER) do
22
22
  mon_enter
23
23
 
24
24
  begin
25
- Thread.handle_interrupt(EXCEPTION_IMMEDIATE) do
26
- yield
27
- end
25
+ Thread.handle_interrupt(EXCEPTION_IMMEDIATE, &block)
28
26
  ensure
29
27
  mon_exit
30
28
  end
@@ -215,9 +215,9 @@ module ActiveSupport
215
215
  @waiting.any? { |t, (p, _)| compatible.include?(p) && @waiting.all? { |t2, (_, c2)| t == t2 || c2.include?(p) } }
216
216
  end
217
217
 
218
- def wait_for(method)
218
+ def wait_for(method, &block)
219
219
  @sleeping[Thread.current] = method
220
- @cv.wait_while { yield }
220
+ @cv.wait_while(&block)
221
221
  ensure
222
222
  @sleeping.delete Thread.current
223
223
  end
@@ -94,17 +94,19 @@ module ActiveSupport
94
94
  # User.new.allowed_access = true # => NoMethodError
95
95
  # User.new.allowed_access # => NoMethodError
96
96
  #
97
- # Also you can pass a block to set up the attribute with a default value.
97
+ # Also you can pass <tt>default</tt> or a block to set up the attribute with a default value.
98
98
  #
99
99
  # class User
100
100
  # include ActiveSupport::Configurable
101
+ # config_accessor :allowed_access, default: false
101
102
  # config_accessor :hair_colors do
102
103
  # [:brown, :black, :blonde, :red]
103
104
  # end
104
105
  # end
105
106
  #
107
+ # User.allowed_access # => false
106
108
  # User.hair_colors # => [:brown, :black, :blonde, :red]
107
- def config_accessor(*names, instance_reader: true, instance_writer: true, instance_accessor: true) # :doc:
109
+ def config_accessor(*names, instance_reader: true, instance_writer: true, instance_accessor: true, default: nil) # :doc:
108
110
  names.each do |name|
109
111
  raise NameError.new("invalid config attribute name") unless /\A[_A-Za-z]\w*\z/.match?(name)
110
112
 
@@ -118,7 +120,8 @@ module ActiveSupport
118
120
  class_eval reader, __FILE__, reader_line if instance_reader
119
121
  class_eval writer, __FILE__, writer_line if instance_writer
120
122
  end
121
- send("#{name}=", yield) if block_given?
123
+
124
+ send("#{name}=", block_given? ? yield : default)
122
125
  end
123
126
  end
124
127
  private :config_accessor
@@ -38,7 +38,7 @@ module ActiveSupport
38
38
 
39
39
  File.read(content_path).tap do |content|
40
40
  if content.include?("\u00A0")
41
- warn "File contains invisible non-breaking spaces, you may want to remove those"
41
+ warn "#{content_path} contains invisible non-breaking spaces, you may want to remove those"
42
42
  end
43
43
  end
44
44
  end
@@ -47,11 +47,7 @@ class Array
47
47
  def excluding(*elements)
48
48
  self - elements.flatten(1)
49
49
  end
50
-
51
- # Alias for #excluding.
52
- def without(*elements)
53
- excluding(*elements)
54
- end
50
+ alias :without :excluding
55
51
 
56
52
  # Equal to <tt>self[1]</tt>.
57
53
  #
@@ -16,12 +16,12 @@ class Array
16
16
  #
17
17
  # ==== Options
18
18
  #
19
- # * <tt>:words_connector</tt> - The sign or word used to join the elements
20
- # in arrays with two or more elements (default: ", ").
21
- # * <tt>:two_words_connector</tt> - The sign or word used to join the elements
22
- # in arrays with two elements (default: " and ").
19
+ # * <tt>:words_connector</tt> - The sign or word used to join all but the last
20
+ # element in arrays with three or more elements (default: ", ").
23
21
  # * <tt>:last_word_connector</tt> - The sign or word used to join the last element
24
22
  # in arrays with three or more elements (default: ", and ").
23
+ # * <tt>:two_words_connector</tt> - The sign or word used to join the elements
24
+ # in arrays with two elements (default: " and ").
25
25
  # * <tt>:locale</tt> - If +i18n+ is available, you can set a locale and use
26
26
  # the connector options defined on the 'support.array' namespace in the
27
27
  # corresponding dictionary file.
@@ -66,7 +66,7 @@ class Array
66
66
  two_words_connector: " and ",
67
67
  last_word_connector: ", and "
68
68
  }
69
- if defined?(I18n)
69
+ if options[:locale] != false && defined?(I18n)
70
70
  i18n_connectors = I18n.translate(:'support.array', locale: options[:locale], default: {})
71
71
  default_connectors.merge!(i18n_connectors)
72
72
  end
@@ -187,7 +187,7 @@ class Array
187
187
  options[:indent] ||= 2
188
188
  options[:builder] ||= Builder::XmlMarkup.new(indent: options[:indent])
189
189
  options[:root] ||= \
190
- if first.class != Hash && all? { |e| e.is_a?(first.class) }
190
+ if first.class != Hash && all?(first.class)
191
191
  underscored = ActiveSupport::Inflector.underscore(first.class.name)
192
192
  ActiveSupport::Inflector.pluralize(underscored).tr("/", "_")
193
193
  else
@@ -19,7 +19,7 @@ class Array
19
19
  # ["1", "2"]
20
20
  # ["3", "4"]
21
21
  # ["5"]
22
- def in_groups_of(number, fill_with = nil)
22
+ def in_groups_of(number, fill_with = nil, &block)
23
23
  if number.to_i <= 0
24
24
  raise ArgumentError,
25
25
  "Group size must be a positive integer, was #{number.inspect}"
@@ -36,7 +36,7 @@ class Array
36
36
  end
37
37
 
38
38
  if block_given?
39
- collection.each_slice(number) { |slice| yield(slice) }
39
+ collection.each_slice(number, &block)
40
40
  else
41
41
  collection.each_slice(number).to_a
42
42
  end
@@ -59,7 +59,7 @@ class Array
59
59
  # ["1", "2", "3"]
60
60
  # ["4", "5"]
61
61
  # ["6", "7"]
62
- def in_groups(number, fill_with = nil)
62
+ def in_groups(number, fill_with = nil, &block)
63
63
  # size.div number gives minor group size;
64
64
  # size % number gives how many objects need extra accommodation;
65
65
  # each group hold either division or division + 1 items.
@@ -79,7 +79,7 @@ class Array
79
79
  end
80
80
 
81
81
  if block_given?
82
- groups.each { |g| yield(g) }
82
+ groups.each(&block)
83
83
  else
84
84
  groups
85
85
  end
@@ -90,11 +90,11 @@ class Array
90
90
  #
91
91
  # [1, 2, 3, 4, 5].split(3) # => [[1, 2], [4, 5]]
92
92
  # (1..10).to_a.split { |i| i % 3 == 0 } # => [[1, 2], [4, 5], [7, 8], [10]]
93
- def split(value = nil)
93
+ def split(value = nil, &block)
94
94
  arr = dup
95
95
  result = []
96
96
  if block_given?
97
- while (idx = arr.index { |i| yield i })
97
+ while (idx = arr.index(&block))
98
98
  result << arr.shift(idx)
99
99
  arr.shift
100
100
  end
@@ -4,7 +4,7 @@ require "bigdecimal"
4
4
  require "bigdecimal/util"
5
5
 
6
6
  module ActiveSupport
7
- module BigDecimalWithDefaultFormat #:nodoc:
7
+ module BigDecimalWithDefaultFormat # :nodoc:
8
8
  def to_s(format = "F")
9
9
  super(format)
10
10
  end
@@ -2,7 +2,7 @@
2
2
 
3
3
  require "date"
4
4
 
5
- class Date #:nodoc:
5
+ class Date # :nodoc:
6
6
  # No Date is blank:
7
7
  #
8
8
  # Date.today.blank? # => false
@@ -87,7 +87,7 @@ class Date
87
87
  end
88
88
  alias :at_end_of_day :end_of_day
89
89
 
90
- def plus_with_duration(other) #:nodoc:
90
+ def plus_with_duration(other) # :nodoc:
91
91
  if ActiveSupport::Duration === other
92
92
  other.since(self)
93
93
  else
@@ -97,7 +97,7 @@ class Date
97
97
  alias_method :plus_without_duration, :+
98
98
  alias_method :+, :plus_with_duration
99
99
 
100
- def minus_with_duration(other) #:nodoc:
100
+ def minus_with_duration(other) # :nodoc:
101
101
  if ActiveSupport::Duration === other
102
102
  plus_with_duration(-other)
103
103
  else
@@ -2,7 +2,7 @@
2
2
 
3
3
  require "date"
4
4
 
5
- class DateTime #:nodoc:
5
+ class DateTime # :nodoc:
6
6
  # No DateTime is ever blank:
7
7
  #
8
8
  # DateTime.now.blank? # => false
@@ -1,28 +1,28 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "securerandom"
4
- require "digest"
4
+ require "openssl"
5
5
 
6
6
  module Digest
7
7
  module UUID
8
- DNS_NAMESPACE = "k\xA7\xB8\x10\x9D\xAD\x11\xD1\x80\xB4\x00\xC0O\xD40\xC8" #:nodoc:
9
- URL_NAMESPACE = "k\xA7\xB8\x11\x9D\xAD\x11\xD1\x80\xB4\x00\xC0O\xD40\xC8" #:nodoc:
10
- OID_NAMESPACE = "k\xA7\xB8\x12\x9D\xAD\x11\xD1\x80\xB4\x00\xC0O\xD40\xC8" #:nodoc:
11
- X500_NAMESPACE = "k\xA7\xB8\x14\x9D\xAD\x11\xD1\x80\xB4\x00\xC0O\xD40\xC8" #:nodoc:
8
+ DNS_NAMESPACE = "k\xA7\xB8\x10\x9D\xAD\x11\xD1\x80\xB4\x00\xC0O\xD40\xC8" # :nodoc:
9
+ URL_NAMESPACE = "k\xA7\xB8\x11\x9D\xAD\x11\xD1\x80\xB4\x00\xC0O\xD40\xC8" # :nodoc:
10
+ OID_NAMESPACE = "k\xA7\xB8\x12\x9D\xAD\x11\xD1\x80\xB4\x00\xC0O\xD40\xC8" # :nodoc:
11
+ X500_NAMESPACE = "k\xA7\xB8\x14\x9D\xAD\x11\xD1\x80\xB4\x00\xC0O\xD40\xC8" # :nodoc:
12
12
 
13
13
  # Generates a v5 non-random UUID (Universally Unique IDentifier).
14
14
  #
15
- # Using Digest::MD5 generates version 3 UUIDs; Digest::SHA1 generates version 5 UUIDs.
15
+ # Using OpenSSL::Digest::MD5 generates version 3 UUIDs; OpenSSL::Digest::SHA1 generates version 5 UUIDs.
16
16
  # uuid_from_hash always generates the same UUID for a given name and namespace combination.
17
17
  #
18
18
  # See RFC 4122 for details of UUID at: https://www.ietf.org/rfc/rfc4122.txt
19
19
  def self.uuid_from_hash(hash_class, uuid_namespace, name)
20
- if hash_class == Digest::MD5
20
+ if hash_class == Digest::MD5 || hash_class == OpenSSL::Digest::MD5
21
21
  version = 3
22
- elsif hash_class == Digest::SHA1
22
+ elsif hash_class == Digest::SHA1 || hash_class == OpenSSL::Digest::SHA1
23
23
  version = 5
24
24
  else
25
- raise ArgumentError, "Expected Digest::SHA1 or Digest::MD5, got #{hash_class.name}."
25
+ raise ArgumentError, "Expected OpenSSL::Digest::SHA1 or OpenSSL::Digest::MD5, got #{hash_class.name}."
26
26
  end
27
27
 
28
28
  hash = hash_class.new
@@ -36,14 +36,14 @@ module Digest
36
36
  "%08x-%04x-%04x-%04x-%04x%08x" % ary
37
37
  end
38
38
 
39
- # Convenience method for uuid_from_hash using Digest::MD5.
39
+ # Convenience method for uuid_from_hash using OpenSSL::Digest::MD5.
40
40
  def self.uuid_v3(uuid_namespace, name)
41
- uuid_from_hash(Digest::MD5, uuid_namespace, name)
41
+ uuid_from_hash(OpenSSL::Digest::MD5, uuid_namespace, name)
42
42
  end
43
43
 
44
- # Convenience method for uuid_from_hash using Digest::SHA1.
44
+ # Convenience method for uuid_from_hash using OpenSSL::Digest::SHA1.
45
45
  def self.uuid_v5(uuid_namespace, name)
46
- uuid_from_hash(Digest::SHA1, uuid_namespace, name)
46
+ uuid_from_hash(OpenSSL::Digest::SHA1, uuid_namespace, name)
47
47
  end
48
48
 
49
49
  # Convenience method for SecureRandom.uuid.
@@ -4,6 +4,10 @@ module Enumerable
4
4
  INDEX_WITH_DEFAULT = Object.new
5
5
  private_constant :INDEX_WITH_DEFAULT
6
6
 
7
+ # Error generated by +sole+ when called on an enumerable that doesn't have
8
+ # exactly one item.
9
+ class SoleItemExpectedError < StandardError; end
10
+
7
11
  # Enumerable#sum was added in Ruby 2.4, but it only works with Numeric elements
8
12
  # when we omit an identity.
9
13
 
@@ -16,6 +20,22 @@ module Enumerable
16
20
 
17
21
  # :startdoc:
18
22
 
23
+ # Calculates the minimum from the extracted elements.
24
+ #
25
+ # payments = [Payment.new(5), Payment.new(15), Payment.new(10)]
26
+ # payments.minimum(:price) # => 5
27
+ def minimum(key)
28
+ map(&key).min
29
+ end
30
+
31
+ # Calculates the maximum from the extracted elements.
32
+ #
33
+ # payments = [Payment.new(5), Payment.new(15), Payment.new(10)]
34
+ # payments.maximum(:price) # => 15
35
+ def maximum(key)
36
+ map(&key).max
37
+ end
38
+
19
39
  # Calculates a sum from the elements.
20
40
  #
21
41
  # payments.sum { |p| p.price * p.tax_rate }
@@ -28,8 +48,8 @@ module Enumerable
28
48
  # It can also calculate the sum without the use of a block.
29
49
  #
30
50
  # [5, 15, 10].sum # => 30
31
- # ['foo', 'bar'].sum # => "foobar"
32
- # [[1, 2], [3, 1, 5]].sum # => [1, 2, 3, 1, 5]
51
+ # ['foo', 'bar'].sum('') # => "foobar"
52
+ # [[1, 2], [3, 1, 5]].sum([]) # => [1, 2, 3, 1, 5]
33
53
  #
34
54
  # The default sum of an empty list is zero. You can override this default:
35
55
  #
@@ -38,8 +58,19 @@ module Enumerable
38
58
  if identity
39
59
  _original_sum_with_required_identity(identity, &block)
40
60
  elsif block_given?
41
- map(&block).sum(identity)
61
+ map(&block).sum
62
+ # we check `first(1) == []` to check if we have an
63
+ # empty Enumerable; checking `empty?` would return
64
+ # true for `[nil]`, which we want to deprecate to
65
+ # keep consistent with Ruby
66
+ elsif first.is_a?(Numeric) || first(1) == []
67
+ identity ||= 0
68
+ _original_sum_with_required_identity(identity, &block)
42
69
  else
70
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
71
+ Rails 7.0 has deprecated Enumerable.sum in favor of Ruby's native implementation available since 2.4.
72
+ Sum of non-numeric elements requires an initial argument.
73
+ MSG
43
74
  inject(:+) || 0
44
75
  end
45
76
  end
@@ -136,11 +167,7 @@ module Enumerable
136
167
  elements.flatten!(1)
137
168
  reject { |element| elements.include?(element) }
138
169
  end
139
-
140
- # Alias for #excluding.
141
- def without(*elements)
142
- excluding(*elements)
143
- end
170
+ alias :without :excluding
144
171
 
145
172
  # Extract the given key from each element in the enumerable.
146
173
  #
@@ -191,11 +218,37 @@ module Enumerable
191
218
  def compact_blank
192
219
  reject(&:blank?)
193
220
  end
221
+
222
+ # Returns a new +Array+ where the order has been set to that provided in the +series+, based on the +key+ of the
223
+ # objects in the original enumerable.
224
+ #
225
+ # [ Person.find(5), Person.find(3), Person.find(1) ].in_order_of(:id, [ 1, 5, 3 ])
226
+ # => [ Person.find(1), Person.find(5), Person.find(3) ]
227
+ #
228
+ # If the +series+ include keys that have no corresponding element in the Enumerable, these are ignored.
229
+ # If the Enumerable has additional elements that aren't named in the +series+, these are not included in the result.
230
+ def in_order_of(key, series)
231
+ index_by(&key).values_at(*series).compact
232
+ end
233
+
234
+ # Returns the sole item in the enumerable. If there are no items, or more
235
+ # than one item, raises +Enumerable::SoleItemExpectedError+.
236
+ #
237
+ # ["x"].sole # => "x"
238
+ # Set.new.sole # => Enumerable::SoleItemExpectedError: no item found
239
+ # { a: 1, b: 2 }.sole # => Enumerable::SoleItemExpectedError: multiple items found
240
+ def sole
241
+ case count
242
+ when 1 then return first # rubocop:disable Style/RedundantReturn
243
+ when 0 then raise SoleItemExpectedError, "no item found"
244
+ when 2.. then raise SoleItemExpectedError, "multiple items found"
245
+ end
246
+ end
194
247
  end
195
248
 
196
249
  class Hash
197
250
  # Hash#reject has its own definition, so this needs one too.
198
- def compact_blank #:nodoc:
251
+ def compact_blank # :nodoc:
199
252
  reject { |_k, v| v.blank? }
200
253
  end
201
254
 
@@ -211,7 +264,7 @@ class Hash
211
264
  end
212
265
  end
213
266
 
214
- class Range #:nodoc:
267
+ class Range # :nodoc:
215
268
  # Optimize range sum to use arithmetic progression if a block is not given and
216
269
  # we have a range of numeric values.
217
270
  def sum(identity = nil)
@@ -236,8 +289,7 @@ using Module.new {
236
289
  end
237
290
  }
238
291
 
239
- class Array #:nodoc:
240
- # Array#sum was added in Ruby 2.4 but it only works with Numeric elements.
292
+ class Array # :nodoc:
241
293
  def sum(init = nil, &block)
242
294
  if init.is_a?(Numeric) || first.is_a?(Numeric)
243
295
  init ||= 0
@@ -53,7 +53,7 @@ class File
53
53
  end
54
54
 
55
55
  # Private utility method.
56
- def self.probe_stat_in(dir) #:nodoc:
56
+ def self.probe_stat_in(dir) # :nodoc:
57
57
  basename = [
58
58
  ".permissions_check",
59
59
  Thread.current.object_id,
@@ -116,7 +116,7 @@ class Hash
116
116
  def _deep_transform_keys_in_object(object, &block)
117
117
  case object
118
118
  when Hash
119
- object.each_with_object({}) do |(key, value), result|
119
+ object.each_with_object(self.class.new) do |(key, value), result|
120
120
  result[yield(key)] = _deep_transform_keys_in_object(value, &block)
121
121
  end
122
122
  when Array
@@ -11,14 +11,14 @@ module Kernel
11
11
  # end
12
12
  #
13
13
  # noisy_call # warning voiced
14
- def silence_warnings
15
- with_warnings(nil) { yield }
14
+ def silence_warnings(&block)
15
+ with_warnings(nil, &block)
16
16
  end
17
17
 
18
18
  # Sets $VERBOSE to +true+ for the duration of the block and back to its
19
19
  # original value afterwards.
20
- def enable_warnings
21
- with_warnings(true) { yield }
20
+ def enable_warnings(&block)
21
+ with_warnings(true, &block)
22
22
  end
23
23
 
24
24
  # Sets $VERBOSE for the duration of the block and back to its original
@@ -199,13 +199,7 @@ class Module
199
199
 
200
200
  # Attribute writer methods only accept one argument. Makes sure []=
201
201
  # methods still accept two arguments.
202
- definition = if /[^\]]=$/.match?(method)
203
- "arg"
204
- elsif RUBY_VERSION >= "2.7"
205
- "..."
206
- else
207
- "*args, &block"
208
- end
202
+ definition = /[^\]]=\z/.match?(method) ? "arg" : "..."
209
203
 
210
204
  # The following generated method calls the target exactly once, storing
211
205
  # the returned value in a dummy variable.
@@ -324,7 +318,7 @@ class Module
324
318
  end
325
319
  end
326
320
  end
327
- ruby2_keywords(:method_missing) if respond_to?(:ruby2_keywords, true)
321
+ ruby2_keywords(:method_missing)
328
322
  RUBY
329
323
  end
330
324
  end
@@ -53,13 +53,7 @@ class NameError
53
53
  UNBOUND_METHOD_MODULE_NAME = Module.instance_method(:name)
54
54
  private_constant :UNBOUND_METHOD_MODULE_NAME
55
55
 
56
- if UnboundMethod.method_defined?(:bind_call)
57
- def real_mod_name(mod)
58
- UNBOUND_METHOD_MODULE_NAME.bind_call(mod)
59
- end
60
- else
61
- def real_mod_name(mod)
62
- UNBOUND_METHOD_MODULE_NAME.bind(mod).call
63
- end
56
+ def real_mod_name(mod)
57
+ UNBOUND_METHOD_MODULE_NAME.bind_call(mod)
64
58
  end
65
59
  end
@@ -107,9 +107,9 @@ module ActiveSupport
107
107
  # separator: ',',
108
108
  # significant: false) # => "1,2 Million"
109
109
  def to_s(format = nil, options = nil)
110
+ return super() if format.nil?
111
+
110
112
  case format
111
- when nil
112
- super()
113
113
  when Integer, String
114
114
  super(format)
115
115
  when :phone
@@ -131,7 +131,7 @@ class String
131
131
  end
132
132
  end
133
133
 
134
- class Numeric #:nodoc:
134
+ class Numeric # :nodoc:
135
135
  # No number is blank:
136
136
  #
137
137
  # 1.blank? # => false
@@ -143,7 +143,7 @@ class Numeric #:nodoc:
143
143
  end
144
144
  end
145
145
 
146
- class Time #:nodoc:
146
+ class Time # :nodoc:
147
147
  # No Time is blank:
148
148
  #
149
149
  # Time.now.blank? # => false
@@ -43,7 +43,7 @@ class Hash
43
43
  def deep_dup
44
44
  hash = dup
45
45
  each_pair do |key, value|
46
- if (::String === key && key.frozen?) || ::Symbol === key
46
+ if ::String === key || ::Symbol === key
47
47
  hash[key] = value.deep_dup
48
48
  else
49
49
  hash.delete(key)
@@ -47,3 +47,14 @@ class UnboundMethod
47
47
  false
48
48
  end
49
49
  end
50
+
51
+ require "singleton"
52
+
53
+ module Singleton
54
+ # Singleton instances are not duplicable:
55
+ #
56
+ # Class.new.include(Singleton).instance.dup # TypeError (can't dup instance of singleton
57
+ def duplicable?
58
+ false
59
+ end
60
+ end