activesupport 6.0.3.7 → 7.0.0

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 (204) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +220 -533
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +1 -1
  5. data/lib/active_support/actionable_error.rb +1 -1
  6. data/lib/active_support/array_inquirer.rb +2 -2
  7. data/lib/active_support/backtrace_cleaner.rb +3 -3
  8. data/lib/active_support/benchmarkable.rb +3 -3
  9. data/lib/active_support/cache/file_store.rb +18 -11
  10. data/lib/active_support/cache/mem_cache_store.rb +143 -37
  11. data/lib/active_support/cache/memory_store.rb +56 -28
  12. data/lib/active_support/cache/null_store.rb +10 -2
  13. data/lib/active_support/cache/redis_cache_store.rb +63 -88
  14. data/lib/active_support/cache/strategy/local_cache.rb +46 -57
  15. data/lib/active_support/cache.rb +273 -82
  16. data/lib/active_support/callbacks.rb +226 -118
  17. data/lib/active_support/code_generator.rb +65 -0
  18. data/lib/active_support/concern.rb +49 -5
  19. data/lib/active_support/concurrency/load_interlock_aware_monitor.rb +2 -4
  20. data/lib/active_support/concurrency/share_lock.rb +2 -2
  21. data/lib/active_support/configurable.rb +9 -6
  22. data/lib/active_support/configuration_file.rb +51 -0
  23. data/lib/active_support/core_ext/array/access.rb +1 -5
  24. data/lib/active_support/core_ext/array/conversions.rb +9 -7
  25. data/lib/active_support/core_ext/array/deprecated_conversions.rb +25 -0
  26. data/lib/active_support/core_ext/array/grouping.rb +6 -6
  27. data/lib/active_support/core_ext/array.rb +1 -0
  28. data/lib/active_support/core_ext/benchmark.rb +2 -2
  29. data/lib/active_support/core_ext/big_decimal/conversions.rb +1 -1
  30. data/lib/active_support/core_ext/class/attribute.rb +34 -44
  31. data/lib/active_support/core_ext/class/subclasses.rb +21 -40
  32. data/lib/active_support/core_ext/date/blank.rb +1 -1
  33. data/lib/active_support/core_ext/date/calculations.rb +4 -4
  34. data/lib/active_support/core_ext/date/conversions.rb +5 -4
  35. data/lib/active_support/core_ext/date/deprecated_conversions.rb +26 -0
  36. data/lib/active_support/core_ext/date.rb +1 -0
  37. data/lib/active_support/core_ext/date_and_time/calculations.rb +13 -0
  38. data/lib/active_support/core_ext/date_and_time/compatibility.rb +15 -0
  39. data/lib/active_support/core_ext/date_time/blank.rb +1 -1
  40. data/lib/active_support/core_ext/date_time/conversions.rb +5 -5
  41. data/lib/active_support/core_ext/date_time/deprecated_conversions.rb +22 -0
  42. data/lib/active_support/core_ext/date_time.rb +1 -0
  43. data/lib/active_support/core_ext/digest/uuid.rb +39 -13
  44. data/lib/active_support/core_ext/enumerable.rb +139 -15
  45. data/lib/active_support/core_ext/file/atomic.rb +1 -1
  46. data/lib/active_support/core_ext/hash/conversions.rb +2 -2
  47. data/lib/active_support/core_ext/hash/deep_transform_values.rb +1 -1
  48. data/lib/active_support/core_ext/hash/except.rb +1 -1
  49. data/lib/active_support/core_ext/hash/keys.rb +2 -2
  50. data/lib/active_support/core_ext/hash/slice.rb +3 -2
  51. data/lib/active_support/core_ext/kernel/reporting.rb +4 -4
  52. data/lib/active_support/core_ext/load_error.rb +1 -1
  53. data/lib/active_support/core_ext/module/attr_internal.rb +2 -2
  54. data/lib/active_support/core_ext/module/attribute_accessors.rb +25 -29
  55. data/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb +26 -13
  56. data/lib/active_support/core_ext/module/concerning.rb +8 -2
  57. data/lib/active_support/core_ext/module/delegation.rb +40 -36
  58. data/lib/active_support/core_ext/module/introspection.rb +1 -25
  59. data/lib/active_support/core_ext/name_error.rb +23 -2
  60. data/lib/active_support/core_ext/numeric/conversions.rb +79 -72
  61. data/lib/active_support/core_ext/numeric/deprecated_conversions.rb +60 -0
  62. data/lib/active_support/core_ext/numeric.rb +1 -0
  63. data/lib/active_support/core_ext/object/blank.rb +2 -2
  64. data/lib/active_support/core_ext/object/deep_dup.rb +1 -1
  65. data/lib/active_support/core_ext/object/duplicable.rb +11 -0
  66. data/lib/active_support/core_ext/object/json.rb +42 -26
  67. data/lib/active_support/core_ext/object/to_query.rb +2 -2
  68. data/lib/active_support/core_ext/object/try.rb +20 -20
  69. data/lib/active_support/core_ext/object/with_options.rb +20 -1
  70. data/lib/active_support/core_ext/pathname/existence.rb +21 -0
  71. data/lib/active_support/core_ext/pathname.rb +3 -0
  72. data/lib/active_support/core_ext/range/compare_range.rb +6 -25
  73. data/lib/active_support/core_ext/range/conversions.rb +8 -8
  74. data/lib/active_support/core_ext/range/deprecated_conversions.rb +26 -0
  75. data/lib/active_support/core_ext/range/each.rb +1 -1
  76. data/lib/active_support/core_ext/range/include_time_with_zone.rb +4 -20
  77. data/lib/active_support/core_ext/range.rb +1 -1
  78. data/lib/active_support/core_ext/regexp.rb +8 -1
  79. data/lib/active_support/core_ext/string/access.rb +5 -24
  80. data/lib/active_support/core_ext/string/conversions.rb +1 -0
  81. data/lib/active_support/core_ext/string/filters.rb +1 -1
  82. data/lib/active_support/core_ext/string/inflections.rb +39 -5
  83. data/lib/active_support/core_ext/string/inquiry.rb +1 -0
  84. data/lib/active_support/core_ext/string/multibyte.rb +2 -2
  85. data/lib/active_support/core_ext/string/output_safety.rb +69 -45
  86. data/lib/active_support/core_ext/string/starts_ends_with.rb +2 -2
  87. data/lib/active_support/core_ext/symbol/starts_ends_with.rb +6 -0
  88. data/lib/active_support/core_ext/symbol.rb +3 -0
  89. data/lib/active_support/core_ext/time/calculations.rb +26 -6
  90. data/lib/active_support/core_ext/time/conversions.rb +6 -3
  91. data/lib/active_support/core_ext/time/deprecated_conversions.rb +22 -0
  92. data/lib/active_support/core_ext/time/zones.rb +4 -19
  93. data/lib/active_support/core_ext/time.rb +1 -0
  94. data/lib/active_support/core_ext/uri.rb +3 -23
  95. data/lib/active_support/core_ext.rb +2 -1
  96. data/lib/active_support/current_attributes/test_helper.rb +13 -0
  97. data/lib/active_support/current_attributes.rb +39 -16
  98. data/lib/active_support/dependencies/interlock.rb +10 -18
  99. data/lib/active_support/dependencies/require_dependency.rb +28 -0
  100. data/lib/active_support/dependencies.rb +58 -764
  101. data/lib/active_support/deprecation/behaviors.rb +19 -3
  102. data/lib/active_support/deprecation/disallowed.rb +56 -0
  103. data/lib/active_support/deprecation/instance_delegator.rb +0 -1
  104. data/lib/active_support/deprecation/method_wrappers.rb +6 -5
  105. data/lib/active_support/deprecation/proxy_wrappers.rb +4 -4
  106. data/lib/active_support/deprecation/reporting.rb +50 -7
  107. data/lib/active_support/deprecation.rb +6 -1
  108. data/lib/active_support/descendants_tracker.rb +177 -64
  109. data/lib/active_support/digest.rb +5 -3
  110. data/lib/active_support/duration/iso8601_parser.rb +3 -3
  111. data/lib/active_support/duration/iso8601_serializer.rb +24 -10
  112. data/lib/active_support/duration.rb +134 -55
  113. data/lib/active_support/encrypted_configuration.rb +11 -1
  114. data/lib/active_support/encrypted_file.rb +20 -3
  115. data/lib/active_support/environment_inquirer.rb +20 -0
  116. data/lib/active_support/error_reporter.rb +117 -0
  117. data/lib/active_support/evented_file_update_checker.rb +70 -134
  118. data/lib/active_support/execution_context/test_helper.rb +13 -0
  119. data/lib/active_support/execution_context.rb +53 -0
  120. data/lib/active_support/execution_wrapper.rb +30 -4
  121. data/lib/active_support/executor/test_helper.rb +7 -0
  122. data/lib/active_support/fork_tracker.rb +71 -0
  123. data/lib/active_support/gem_version.rb +3 -3
  124. data/lib/active_support/hash_with_indifferent_access.rb +51 -25
  125. data/lib/active_support/html_safe_translation.rb +43 -0
  126. data/lib/active_support/i18n.rb +1 -0
  127. data/lib/active_support/i18n_railtie.rb +14 -19
  128. data/lib/active_support/inflector/inflections.rb +24 -9
  129. data/lib/active_support/inflector/methods.rb +29 -49
  130. data/lib/active_support/inflector/transliterate.rb +4 -4
  131. data/lib/active_support/isolated_execution_state.rb +56 -0
  132. data/lib/active_support/json/decoding.rb +4 -4
  133. data/lib/active_support/json/encoding.rb +8 -4
  134. data/lib/active_support/key_generator.rb +19 -2
  135. data/lib/active_support/locale/en.yml +8 -4
  136. data/lib/active_support/log_subscriber.rb +21 -3
  137. data/lib/active_support/logger.rb +1 -1
  138. data/lib/active_support/logger_silence.rb +2 -26
  139. data/lib/active_support/logger_thread_safe_level.rb +34 -21
  140. data/lib/active_support/message_encryptor.rb +12 -10
  141. data/lib/active_support/message_verifier.rb +50 -18
  142. data/lib/active_support/messages/metadata.rb +11 -3
  143. data/lib/active_support/messages/rotation_configuration.rb +2 -1
  144. data/lib/active_support/messages/rotator.rb +6 -5
  145. data/lib/active_support/multibyte/chars.rb +13 -52
  146. data/lib/active_support/multibyte/unicode.rb +1 -87
  147. data/lib/active_support/multibyte.rb +1 -1
  148. data/lib/active_support/notifications/fanout.rb +110 -69
  149. data/lib/active_support/notifications/instrumenter.rb +37 -29
  150. data/lib/active_support/notifications.rb +47 -26
  151. data/lib/active_support/number_helper/number_converter.rb +2 -4
  152. data/lib/active_support/number_helper/number_to_currency_converter.rb +10 -9
  153. data/lib/active_support/number_helper/number_to_delimited_converter.rb +1 -1
  154. data/lib/active_support/number_helper/number_to_human_converter.rb +1 -1
  155. data/lib/active_support/number_helper/number_to_human_size_converter.rb +2 -2
  156. data/lib/active_support/number_helper/number_to_phone_converter.rb +1 -1
  157. data/lib/active_support/number_helper/number_to_rounded_converter.rb +9 -5
  158. data/lib/active_support/number_helper/rounding_helper.rb +12 -32
  159. data/lib/active_support/number_helper.rb +29 -16
  160. data/lib/active_support/option_merger.rb +9 -16
  161. data/lib/active_support/ordered_hash.rb +1 -1
  162. data/lib/active_support/ordered_options.rb +8 -2
  163. data/lib/active_support/parameter_filter.rb +21 -11
  164. data/lib/active_support/per_thread_registry.rb +6 -1
  165. data/lib/active_support/rails.rb +1 -4
  166. data/lib/active_support/railtie.rb +77 -5
  167. data/lib/active_support/rescuable.rb +6 -6
  168. data/lib/active_support/ruby_features.rb +7 -0
  169. data/lib/active_support/secure_compare_rotator.rb +51 -0
  170. data/lib/active_support/security_utils.rb +19 -12
  171. data/lib/active_support/string_inquirer.rb +2 -2
  172. data/lib/active_support/subscriber.rb +19 -25
  173. data/lib/active_support/tagged_logging.rb +31 -6
  174. data/lib/active_support/test_case.rb +9 -21
  175. data/lib/active_support/testing/assertions.rb +49 -12
  176. data/lib/active_support/testing/deprecation.rb +52 -1
  177. data/lib/active_support/testing/isolation.rb +2 -2
  178. data/lib/active_support/testing/method_call_assertions.rb +5 -5
  179. data/lib/active_support/testing/parallelization/server.rb +82 -0
  180. data/lib/active_support/testing/parallelization/worker.rb +103 -0
  181. data/lib/active_support/testing/parallelization.rb +16 -95
  182. data/lib/active_support/testing/parallelize_executor.rb +76 -0
  183. data/lib/active_support/testing/stream.rb +3 -5
  184. data/lib/active_support/testing/tagged_logging.rb +1 -1
  185. data/lib/active_support/testing/time_helpers.rb +53 -5
  186. data/lib/active_support/time_with_zone.rb +120 -55
  187. data/lib/active_support/values/time_zone.rb +49 -18
  188. data/lib/active_support/xml_mini/jdom.rb +1 -1
  189. data/lib/active_support/xml_mini/libxml.rb +5 -5
  190. data/lib/active_support/xml_mini/libxmlsax.rb +1 -1
  191. data/lib/active_support/xml_mini/nokogiri.rb +4 -4
  192. data/lib/active_support/xml_mini/nokogirisax.rb +1 -1
  193. data/lib/active_support/xml_mini/rexml.rb +9 -2
  194. data/lib/active_support/xml_mini.rb +5 -4
  195. data/lib/active_support.rb +29 -1
  196. metadata +46 -45
  197. data/lib/active_support/core_ext/array/prepend_and_append.rb +0 -5
  198. data/lib/active_support/core_ext/hash/compact.rb +0 -5
  199. data/lib/active_support/core_ext/hash/transform_values.rb +0 -5
  200. data/lib/active_support/core_ext/marshal.rb +0 -24
  201. data/lib/active_support/core_ext/module/reachable.rb +0 -6
  202. data/lib/active_support/core_ext/numeric/inquiry.rb +0 -5
  203. data/lib/active_support/core_ext/range/include_range.rb +0 -9
  204. data/lib/active_support/dependencies/zeitwerk_integration.rb +0 -117
@@ -9,14 +9,14 @@ require "active_support/values/time_zone"
9
9
  class DateTime
10
10
  # Convert to a formatted string. See Time::DATE_FORMATS for predefined formats.
11
11
  #
12
- # This method is aliased to <tt>to_s</tt>.
12
+ # This method is aliased to <tt>to_fs</tt>.
13
13
  #
14
14
  # === Examples
15
15
  # datetime = DateTime.civil(2007, 12, 4, 0, 0, 0, 0) # => Tue, 04 Dec 2007 00:00:00 +0000
16
16
  #
17
17
  # datetime.to_formatted_s(:db) # => "2007-12-04 00:00:00"
18
- # datetime.to_s(:db) # => "2007-12-04 00:00:00"
19
- # datetime.to_s(:number) # => "20071204000000"
18
+ # datetime.to_fs(:db) # => "2007-12-04 00:00:00"
19
+ # datetime.to_formatted_s(:number) # => "20071204000000"
20
20
  # datetime.to_formatted_s(:short) # => "04 Dec 00:00"
21
21
  # datetime.to_formatted_s(:long) # => "December 04, 2007 00:00"
22
22
  # datetime.to_formatted_s(:long_ordinal) # => "December 4th, 2007 00:00"
@@ -39,8 +39,8 @@ class DateTime
39
39
  to_default_s
40
40
  end
41
41
  end
42
+ alias_method :to_fs, :to_formatted_s
42
43
  alias_method :to_default_s, :to_s if instance_methods(false).include?(:to_s)
43
- alias_method :to_s, :to_formatted_s
44
44
 
45
45
  # Returns a formatted string of the offset from UTC, or an alternative
46
46
  # string if the time zone is already UTC.
@@ -54,7 +54,7 @@ class DateTime
54
54
 
55
55
  # Overrides the default inspect method with a human readable one, e.g., "Mon, 21 Feb 2005 14:30:00 +0000".
56
56
  def readable_inspect
57
- to_s(:rfc822)
57
+ to_formatted_s(:rfc822)
58
58
  end
59
59
  alias_method :default_inspect, :inspect
60
60
  alias_method :inspect, :readable_inspect
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "date"
4
+
5
+ class DateTime
6
+ NOT_SET = Object.new # :nodoc:
7
+ def to_s(format = NOT_SET) # :nodoc:
8
+ if formatter = ::Time::DATE_FORMATS[format]
9
+ ActiveSupport::Deprecation.warn(
10
+ "DateTime#to_s(#{format.inspect}) is deprecated. Please use DateTime#to_formatted_s(#{format.inspect}) instead."
11
+ )
12
+ formatter.respond_to?(:call) ? formatter.call(self).to_s : strftime(formatter)
13
+ elsif format == NOT_SET
14
+ to_default_s
15
+ else
16
+ ActiveSupport::Deprecation.warn(
17
+ "DateTime#to_s(#{format.inspect}) is deprecated. Please use DateTime#to_formatted_s(#{format.inspect}) instead."
18
+ )
19
+ to_default_s
20
+ end
21
+ end
22
+ end
@@ -5,3 +5,4 @@ require "active_support/core_ext/date_time/blank"
5
5
  require "active_support/core_ext/date_time/calculations"
6
6
  require "active_support/core_ext/date_time/compatibility"
7
7
  require "active_support/core_ext/date_time/conversions"
8
+ require "active_support/core_ext/date_time/deprecated_conversions" unless ENV["RAILS_DISABLE_DEPRECATED_TO_S_CONVERSION"]
@@ -1,29 +1,34 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "securerandom"
4
+ require "openssl"
4
5
 
5
6
  module Digest
6
7
  module UUID
7
- DNS_NAMESPACE = "k\xA7\xB8\x10\x9D\xAD\x11\xD1\x80\xB4\x00\xC0O\xD40\xC8" #:nodoc:
8
- URL_NAMESPACE = "k\xA7\xB8\x11\x9D\xAD\x11\xD1\x80\xB4\x00\xC0O\xD40\xC8" #:nodoc:
9
- OID_NAMESPACE = "k\xA7\xB8\x12\x9D\xAD\x11\xD1\x80\xB4\x00\xC0O\xD40\xC8" #:nodoc:
10
- 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
+
13
+ mattr_accessor :use_rfc4122_namespaced_uuids, instance_accessor: false, default: false
11
14
 
12
15
  # Generates a v5 non-random UUID (Universally Unique IDentifier).
13
16
  #
14
- # Using Digest::MD5 generates version 3 UUIDs; Digest::SHA1 generates version 5 UUIDs.
17
+ # Using OpenSSL::Digest::MD5 generates version 3 UUIDs; OpenSSL::Digest::SHA1 generates version 5 UUIDs.
15
18
  # uuid_from_hash always generates the same UUID for a given name and namespace combination.
16
19
  #
17
20
  # See RFC 4122 for details of UUID at: https://www.ietf.org/rfc/rfc4122.txt
18
- def self.uuid_from_hash(hash_class, uuid_namespace, name)
19
- if hash_class == Digest::MD5
21
+ def self.uuid_from_hash(hash_class, namespace, name)
22
+ if hash_class == Digest::MD5 || hash_class == OpenSSL::Digest::MD5
20
23
  version = 3
21
- elsif hash_class == Digest::SHA1
24
+ elsif hash_class == Digest::SHA1 || hash_class == OpenSSL::Digest::SHA1
22
25
  version = 5
23
26
  else
24
- raise ArgumentError, "Expected Digest::SHA1 or Digest::MD5, got #{hash_class.name}."
27
+ raise ArgumentError, "Expected OpenSSL::Digest::SHA1 or OpenSSL::Digest::MD5, got #{hash_class.name}."
25
28
  end
26
29
 
30
+ uuid_namespace = pack_uuid_namespace(namespace)
31
+
27
32
  hash = hash_class.new
28
33
  hash.update(uuid_namespace)
29
34
  hash.update(name)
@@ -35,19 +40,40 @@ module Digest
35
40
  "%08x-%04x-%04x-%04x-%04x%08x" % ary
36
41
  end
37
42
 
38
- # Convenience method for uuid_from_hash using Digest::MD5.
43
+ # Convenience method for uuid_from_hash using OpenSSL::Digest::MD5.
39
44
  def self.uuid_v3(uuid_namespace, name)
40
- uuid_from_hash(Digest::MD5, uuid_namespace, name)
45
+ uuid_from_hash(OpenSSL::Digest::MD5, uuid_namespace, name)
41
46
  end
42
47
 
43
- # Convenience method for uuid_from_hash using Digest::SHA1.
48
+ # Convenience method for uuid_from_hash using OpenSSL::Digest::SHA1.
44
49
  def self.uuid_v5(uuid_namespace, name)
45
- uuid_from_hash(Digest::SHA1, uuid_namespace, name)
50
+ uuid_from_hash(OpenSSL::Digest::SHA1, uuid_namespace, name)
46
51
  end
47
52
 
48
53
  # Convenience method for SecureRandom.uuid.
49
54
  def self.uuid_v4
50
55
  SecureRandom.uuid
51
56
  end
57
+
58
+ def self.pack_uuid_namespace(namespace)
59
+ if [DNS_NAMESPACE, OID_NAMESPACE, URL_NAMESPACE, X500_NAMESPACE].include?(namespace)
60
+ namespace
61
+ elsif use_rfc4122_namespaced_uuids == true
62
+ match_data = namespace.match(/\A(\h{8})-(\h{4})-(\h{4})-(\h{4})-(\h{4})(\h{8})\z/)
63
+
64
+ raise ArgumentError, "Only UUIDs are valid namespace identifiers" unless match_data.present?
65
+
66
+ match_data.captures.map { |s| s.to_i(16) }.pack("NnnnnN")
67
+ else
68
+ ActiveSupport::Deprecation.warn <<~WARNING.squish
69
+ Providing a namespace ID that is not one of the constants defined on Digest::UUID generates an incorrect UUID value according to RFC 4122.
70
+ To enable the correct behavior, set the Rails.application.config.active_support.use_rfc4122_namespaced_uuids configuration option to true.
71
+ WARNING
72
+
73
+ namespace
74
+ end
75
+ end
76
+
77
+ private_class_method :pack_uuid_namespace
52
78
  end
53
79
  end
@@ -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,13 +58,25 @@ 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
46
77
 
47
- # Convert an enumerable to a hash keying it by the block return value.
78
+ # Convert an enumerable to a hash, using the block result as the key and the
79
+ # element as the value.
48
80
  #
49
81
  # people.index_by(&:login)
50
82
  # # => { "nextangle" => <Person ...>, "chade-" => <Person ...>, ...}
@@ -61,12 +93,19 @@ module Enumerable
61
93
  end
62
94
  end
63
95
 
64
- # Convert an enumerable to a hash keying it with the enumerable items and with the values returned in the block.
96
+ # Convert an enumerable to a hash, using the element as the key and the block
97
+ # result as the value.
65
98
  #
66
99
  # post = Post.new(title: "hey there", body: "what's up?")
67
100
  #
68
101
  # %i( title body ).index_with { |attr_name| post.public_send(attr_name) }
69
102
  # # => { title: "hey there", body: "what's up?" }
103
+ #
104
+ # If an argument is passed instead of a block, it will be used as the value
105
+ # for all elements:
106
+ #
107
+ # %i( created_at updated_at ).index_with(Time.now)
108
+ # # => { created_at: 2020-03-09 22:31:47, updated_at: 2020-03-09 22:31:47 }
70
109
  def index_with(default = INDEX_WITH_DEFAULT)
71
110
  if block_given?
72
111
  result = {}
@@ -128,13 +167,9 @@ module Enumerable
128
167
  elements.flatten!(1)
129
168
  reject { |element| elements.include?(element) }
130
169
  end
170
+ alias :without :excluding
131
171
 
132
- # Alias for #excluding.
133
- def without(*elements)
134
- excluding(*elements)
135
- end
136
-
137
- # Convert an enumerable to an array based on the given key.
172
+ # Extract the given key from each element in the enumerable.
138
173
  #
139
174
  # [{ name: "David" }, { name: "Rafael" }, { name: "Aaron" }].pluck(:name)
140
175
  # # => ["David", "Rafael", "Aaron"]
@@ -145,12 +180,91 @@ module Enumerable
145
180
  if keys.many?
146
181
  map { |element| keys.map { |key| element[key] } }
147
182
  else
148
- map { |element| element[keys.first] }
183
+ key = keys.first
184
+ map { |element| element[key] }
149
185
  end
150
186
  end
187
+
188
+ # Extract the given key from the first element in the enumerable.
189
+ #
190
+ # [{ name: "David" }, { name: "Rafael" }, { name: "Aaron" }].pick(:name)
191
+ # # => "David"
192
+ #
193
+ # [{ id: 1, name: "David" }, { id: 2, name: "Rafael" }].pick(:id, :name)
194
+ # # => [1, "David"]
195
+ def pick(*keys)
196
+ return if none?
197
+
198
+ if keys.many?
199
+ keys.map { |key| first[key] }
200
+ else
201
+ first[keys.first]
202
+ end
203
+ end
204
+
205
+ # Returns a new +Array+ without the blank items.
206
+ # Uses Object#blank? for determining if an item is blank.
207
+ #
208
+ # [1, "", nil, 2, " ", [], {}, false, true].compact_blank
209
+ # # => [1, 2, true]
210
+ #
211
+ # Set.new([nil, "", 1, 2])
212
+ # # => [2, 1] (or [1, 2])
213
+ #
214
+ # When called on a +Hash+, returns a new +Hash+ without the blank values.
215
+ #
216
+ # { a: "", b: 1, c: nil, d: [], e: false, f: true }.compact_blank
217
+ # #=> { b: 1, f: true }
218
+ def compact_blank
219
+ reject(&:blank?)
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
247
+ end
248
+
249
+ class Hash
250
+ # Hash#reject has its own definition, so this needs one too.
251
+ def compact_blank # :nodoc:
252
+ reject { |_k, v| v.blank? }
253
+ end
254
+
255
+ # Removes all blank values from the +Hash+ in place and returns self.
256
+ # Uses Object#blank? for determining if a value is blank.
257
+ #
258
+ # h = { a: "", b: 1, c: nil, d: [], e: false, f: true }
259
+ # h.compact_blank!
260
+ # # => { b: 1, f: true }
261
+ def compact_blank!
262
+ # use delete_if rather than reject! because it always returns self even if nothing changed
263
+ delete_if { |_k, v| v.blank? }
264
+ end
151
265
  end
152
266
 
153
- class Range #:nodoc:
267
+ class Range # :nodoc:
154
268
  # Optimize range sum to use arithmetic progression if a block is not given and
155
269
  # we have a range of numeric values.
156
270
  def sum(identity = nil)
@@ -175,8 +289,7 @@ using Module.new {
175
289
  end
176
290
  }
177
291
 
178
- class Array #:nodoc:
179
- # Array#sum was added in Ruby 2.4 but it only works with Numeric elements.
292
+ class Array # :nodoc:
180
293
  def sum(init = nil, &block)
181
294
  if init.is_a?(Numeric) || first.is_a?(Numeric)
182
295
  init ||= 0
@@ -185,4 +298,15 @@ class Array #:nodoc:
185
298
  super
186
299
  end
187
300
  end
301
+
302
+ # Removes all blank elements from the +Array+ in place and returns self.
303
+ # Uses Object#blank? for determining if an item is blank.
304
+ #
305
+ # a = [1, "", nil, 2, " ", [], {}, false, true]
306
+ # a.compact_blank!
307
+ # # => [1, 2, true]
308
+ def compact_blank!
309
+ # use delete_if rather than reject! because it always returns self even if nothing changed
310
+ delete_if(&:blank?)
311
+ end
188
312
  end
@@ -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,
@@ -1,10 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "active_support/xml_mini"
4
- require "active_support/time"
5
4
  require "active_support/core_ext/object/blank"
6
5
  require "active_support/core_ext/object/to_param"
7
6
  require "active_support/core_ext/object/to_query"
7
+ require "active_support/core_ext/object/try"
8
8
  require "active_support/core_ext/array/wrap"
9
9
  require "active_support/core_ext/hash/reverse_merge"
10
10
  require "active_support/core_ext/string/inflections"
@@ -208,7 +208,7 @@ module ActiveSupport
208
208
  elsif become_empty_string?(value)
209
209
  ""
210
210
  elsif become_hash?(value)
211
- xml_value = Hash[value.map { |k, v| [k, deep_to_h(v)] }]
211
+ xml_value = value.transform_values { |v| deep_to_h(v) }
212
212
 
213
213
  # Turn { files: { file: #<StringIO> } } into { files: #<StringIO> } so it is compatible with
214
214
  # how multipart uploaded files from HTML appear
@@ -21,7 +21,7 @@ class Hash
21
21
  end
22
22
 
23
23
  private
24
- # support methods for deep transforming nested hashes and arrays
24
+ # Support methods for deep transforming nested hashes and arrays.
25
25
  def _deep_transform_values_in_object(object, &block)
26
26
  case object
27
27
  when Hash
@@ -11,7 +11,7 @@ class Hash
11
11
  # @person.update(params[:person].except(:admin))
12
12
  def except(*keys)
13
13
  slice(*self.keys - keys)
14
- end
14
+ end unless method_defined?(:except)
15
15
 
16
16
  # Removes the given keys from hash and returns it.
17
17
  # hash = { a: true, b: false, c: nil }
@@ -112,11 +112,11 @@ class Hash
112
112
  end
113
113
 
114
114
  private
115
- # support methods for deep transforming nested hashes and arrays
115
+ # Support methods for deep transforming nested hashes and arrays.
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
@@ -18,8 +18,9 @@ class Hash
18
18
 
19
19
  # Removes and returns the key/value pairs matching the given keys.
20
20
  #
21
- # { a: 1, b: 2, c: 3, d: 4 }.extract!(:a, :b) # => {:a=>1, :b=>2}
22
- # { a: 1, b: 2 }.extract!(:a, :x) # => {:a=>1}
21
+ # hash = { a: 1, b: 2, c: 3, d: 4 }
22
+ # hash.extract!(:a, :b) # => {:a=>1, :b=>2}
23
+ # hash # => {:c=>3, :d=>4}
23
24
  def extract!(*keys)
24
25
  keys.each_with_object(self.class.new) { |key, result| result[key] = delete(key) if has_key?(key) }
25
26
  end
@@ -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
@@ -4,6 +4,6 @@ class LoadError
4
4
  # Returns true if the given path name (except perhaps for the ".rb"
5
5
  # extension) is the missing file which caused the exception to be raised.
6
6
  def is_missing?(location)
7
- location.sub(/\.rb$/, "") == path.to_s.sub(/\.rb$/, "")
7
+ location.delete_suffix(".rb") == path.to_s.delete_suffix(".rb")
8
8
  end
9
9
  end
@@ -28,9 +28,9 @@ class Module
28
28
  end
29
29
 
30
30
  def attr_internal_define(attr_name, type)
31
- internal_name = attr_internal_ivar_name(attr_name).sub(/\A@/, "")
31
+ internal_name = attr_internal_ivar_name(attr_name).delete_prefix("@")
32
32
  # use native attr_* methods as they are faster on some Ruby implementations
33
- send("attr_#{type}", internal_name)
33
+ public_send("attr_#{type}", internal_name)
34
34
  attr_name, internal_name = "#{attr_name}=", "#{internal_name}=" if type == :writer
35
35
  alias_method attr_name, internal_name
36
36
  remove_method internal_name
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # == Attribute Accessors
4
+ #
3
5
  # Extends the module object with class/module and instance accessors for
4
6
  # class/module attributes, just like the native attr* accessors for instance
5
7
  # attributes.
@@ -48,28 +50,25 @@ class Module
48
50
  # end
49
51
  #
50
52
  # Person.new.hair_colors # => [:brown, :black, :blonde, :red]
51
- def mattr_reader(*syms, instance_reader: true, instance_accessor: true, default: nil)
53
+ def mattr_reader(*syms, instance_reader: true, instance_accessor: true, default: nil, location: nil)
54
+ raise TypeError, "module attributes should be defined directly on class, not singleton" if singleton_class?
55
+ location ||= caller_locations(1, 1).first
56
+
57
+ definition = []
52
58
  syms.each do |sym|
53
59
  raise NameError.new("invalid attribute name: #{sym}") unless /\A[_A-Za-z]\w*\z/.match?(sym)
54
- class_eval(<<-EOS, __FILE__, __LINE__ + 1)
55
- @@#{sym} = nil unless defined? @@#{sym}
56
60
 
57
- def self.#{sym}
58
- @@#{sym}
59
- end
60
- EOS
61
+ definition << "def self.#{sym}; @@#{sym}; end"
61
62
 
62
63
  if instance_reader && instance_accessor
63
- class_eval(<<-EOS, __FILE__, __LINE__ + 1)
64
- def #{sym}
65
- @@#{sym}
66
- end
67
- EOS
64
+ definition << "def #{sym}; @@#{sym}; end"
68
65
  end
69
66
 
70
67
  sym_default_value = (block_given? && default.nil?) ? yield : default
71
- class_variable_set("@@#{sym}", sym_default_value) unless sym_default_value.nil?
68
+ class_variable_set("@@#{sym}", sym_default_value) unless sym_default_value.nil? && class_variable_defined?("@@#{sym}")
72
69
  end
70
+
71
+ module_eval(definition.join(";"), location.path, location.lineno)
73
72
  end
74
73
  alias :cattr_reader :mattr_reader
75
74
 
@@ -115,28 +114,24 @@ class Module
115
114
  # end
116
115
  #
117
116
  # Person.class_variable_get("@@hair_colors") # => [:brown, :black, :blonde, :red]
118
- def mattr_writer(*syms, instance_writer: true, instance_accessor: true, default: nil)
117
+ def mattr_writer(*syms, instance_writer: true, instance_accessor: true, default: nil, location: nil)
118
+ raise TypeError, "module attributes should be defined directly on class, not singleton" if singleton_class?
119
+ location ||= caller_locations(1, 1).first
120
+
121
+ definition = []
119
122
  syms.each do |sym|
120
123
  raise NameError.new("invalid attribute name: #{sym}") unless /\A[_A-Za-z]\w*\z/.match?(sym)
121
- class_eval(<<-EOS, __FILE__, __LINE__ + 1)
122
- @@#{sym} = nil unless defined? @@#{sym}
123
-
124
- def self.#{sym}=(obj)
125
- @@#{sym} = obj
126
- end
127
- EOS
124
+ definition << "def self.#{sym}=(val); @@#{sym} = val; end"
128
125
 
129
126
  if instance_writer && instance_accessor
130
- class_eval(<<-EOS, __FILE__, __LINE__ + 1)
131
- def #{sym}=(obj)
132
- @@#{sym} = obj
133
- end
134
- EOS
127
+ definition << "def #{sym}=(val); @@#{sym} = val; end"
135
128
  end
136
129
 
137
130
  sym_default_value = (block_given? && default.nil?) ? yield : default
138
- send("#{sym}=", sym_default_value) unless sym_default_value.nil?
131
+ class_variable_set("@@#{sym}", sym_default_value) unless sym_default_value.nil? && class_variable_defined?("@@#{sym}")
139
132
  end
133
+
134
+ module_eval(definition.join(";"), location.path, location.lineno)
140
135
  end
141
136
  alias :cattr_writer :mattr_writer
142
137
 
@@ -205,8 +200,9 @@ class Module
205
200
  #
206
201
  # Person.class_variable_get("@@hair_colors") # => [:brown, :black, :blonde, :red]
207
202
  def mattr_accessor(*syms, instance_reader: true, instance_writer: true, instance_accessor: true, default: nil, &blk)
208
- mattr_reader(*syms, instance_reader: instance_reader, instance_accessor: instance_accessor, default: default, &blk)
209
- mattr_writer(*syms, instance_writer: instance_writer, instance_accessor: instance_accessor, default: default)
203
+ location = caller_locations(1, 1).first
204
+ mattr_reader(*syms, instance_reader: instance_reader, instance_accessor: instance_accessor, default: default, location: location, &blk)
205
+ mattr_writer(*syms, instance_writer: instance_writer, instance_accessor: instance_accessor, default: default, location: location)
210
206
  end
211
207
  alias :cattr_accessor :mattr_accessor
212
208
  end