activesupport 5.2.4.3 → 7.0.3

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 (228) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +244 -459
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +4 -3
  5. data/lib/active_support/actionable_error.rb +48 -0
  6. data/lib/active_support/array_inquirer.rb +2 -2
  7. data/lib/active_support/backtrace_cleaner.rb +31 -5
  8. data/lib/active_support/benchmarkable.rb +3 -3
  9. data/lib/active_support/cache/file_store.rb +47 -41
  10. data/lib/active_support/cache/mem_cache_store.rb +151 -40
  11. data/lib/active_support/cache/memory_store.rb +68 -34
  12. data/lib/active_support/cache/null_store.rb +16 -3
  13. data/lib/active_support/cache/redis_cache_store.rb +103 -101
  14. data/lib/active_support/cache/strategy/local_cache.rb +56 -64
  15. data/lib/active_support/cache.rb +333 -116
  16. data/lib/active_support/callbacks.rb +244 -128
  17. data/lib/active_support/code_generator.rb +65 -0
  18. data/lib/active_support/concern.rb +72 -5
  19. data/lib/active_support/concurrency/load_interlock_aware_monitor.rb +16 -0
  20. data/lib/active_support/concurrency/share_lock.rb +2 -3
  21. data/lib/active_support/configurable.rb +15 -16
  22. data/lib/active_support/configuration_file.rb +51 -0
  23. data/lib/active_support/core_ext/array/access.rb +15 -7
  24. data/lib/active_support/core_ext/array/conversions.rb +18 -17
  25. data/lib/active_support/core_ext/array/deprecated_conversions.rb +25 -0
  26. data/lib/active_support/core_ext/array/extract.rb +21 -0
  27. data/lib/active_support/core_ext/array/grouping.rb +6 -6
  28. data/lib/active_support/core_ext/array/inquiry.rb +2 -2
  29. data/lib/active_support/core_ext/array.rb +2 -1
  30. data/lib/active_support/core_ext/benchmark.rb +2 -2
  31. data/lib/active_support/core_ext/big_decimal/conversions.rb +1 -1
  32. data/lib/active_support/core_ext/class/attribute.rb +32 -47
  33. data/lib/active_support/core_ext/class/subclasses.rb +9 -22
  34. data/lib/active_support/core_ext/date/blank.rb +1 -1
  35. data/lib/active_support/core_ext/date/calculations.rb +15 -14
  36. data/lib/active_support/core_ext/date/conversions.rb +16 -15
  37. data/lib/active_support/core_ext/date/deprecated_conversions.rb +26 -0
  38. data/lib/active_support/core_ext/date.rb +1 -0
  39. data/lib/active_support/core_ext/date_and_time/calculations.rb +41 -51
  40. data/lib/active_support/core_ext/date_and_time/compatibility.rb +15 -0
  41. data/lib/active_support/core_ext/date_and_time/zones.rb +0 -1
  42. data/lib/active_support/core_ext/date_time/blank.rb +1 -1
  43. data/lib/active_support/core_ext/date_time/calculations.rb +1 -1
  44. data/lib/active_support/core_ext/date_time/conversions.rb +13 -14
  45. data/lib/active_support/core_ext/date_time/deprecated_conversions.rb +22 -0
  46. data/lib/active_support/core_ext/date_time.rb +1 -0
  47. data/lib/active_support/core_ext/digest/uuid.rb +39 -13
  48. data/lib/active_support/core_ext/enumerable.rb +241 -76
  49. data/lib/active_support/core_ext/file/atomic.rb +3 -1
  50. data/lib/active_support/core_ext/hash/conversions.rb +3 -4
  51. data/lib/active_support/core_ext/hash/deep_transform_values.rb +46 -0
  52. data/lib/active_support/core_ext/hash/except.rb +2 -2
  53. data/lib/active_support/core_ext/hash/indifferent_access.rb +3 -3
  54. data/lib/active_support/core_ext/hash/keys.rb +2 -31
  55. data/lib/active_support/core_ext/hash/slice.rb +6 -27
  56. data/lib/active_support/core_ext/hash.rb +1 -2
  57. data/lib/active_support/core_ext/integer/multiple.rb +1 -1
  58. data/lib/active_support/core_ext/kernel/reporting.rb +4 -4
  59. data/lib/active_support/core_ext/kernel/singleton_class.rb +1 -1
  60. data/lib/active_support/core_ext/kernel.rb +0 -1
  61. data/lib/active_support/core_ext/load_error.rb +1 -1
  62. data/lib/active_support/core_ext/module/attr_internal.rb +2 -2
  63. data/lib/active_support/core_ext/module/attribute_accessors.rb +32 -39
  64. data/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb +35 -28
  65. data/lib/active_support/core_ext/module/concerning.rb +8 -2
  66. data/lib/active_support/core_ext/module/delegation.rb +70 -33
  67. data/lib/active_support/core_ext/module/introspection.rb +16 -15
  68. data/lib/active_support/core_ext/module/redefine_method.rb +8 -17
  69. data/lib/active_support/core_ext/module.rb +0 -1
  70. data/lib/active_support/core_ext/name_error.rb +23 -2
  71. data/lib/active_support/core_ext/numeric/conversions.rb +132 -129
  72. data/lib/active_support/core_ext/numeric/deprecated_conversions.rb +60 -0
  73. data/lib/active_support/core_ext/numeric.rb +1 -1
  74. data/lib/active_support/core_ext/object/acts_like.rb +29 -5
  75. data/lib/active_support/core_ext/object/blank.rb +3 -4
  76. data/lib/active_support/core_ext/object/deep_dup.rb +1 -1
  77. data/lib/active_support/core_ext/object/duplicable.rb +14 -110
  78. data/lib/active_support/core_ext/object/json.rb +44 -27
  79. data/lib/active_support/core_ext/object/to_query.rb +2 -2
  80. data/lib/active_support/core_ext/object/try.rb +24 -14
  81. data/lib/active_support/core_ext/object/with_options.rb +21 -2
  82. data/lib/active_support/core_ext/pathname/existence.rb +21 -0
  83. data/lib/active_support/core_ext/pathname.rb +3 -0
  84. data/lib/active_support/core_ext/range/compare_range.rb +23 -27
  85. data/lib/active_support/core_ext/range/conversions.rb +32 -30
  86. data/lib/active_support/core_ext/range/deprecated_conversions.rb +26 -0
  87. data/lib/active_support/core_ext/range/each.rb +1 -2
  88. data/lib/active_support/core_ext/range/include_time_with_zone.rb +4 -20
  89. data/lib/active_support/core_ext/range/overlaps.rb +1 -1
  90. data/lib/active_support/core_ext/range.rb +1 -1
  91. data/lib/active_support/core_ext/regexp.rb +8 -5
  92. data/lib/active_support/core_ext/securerandom.rb +23 -3
  93. data/lib/active_support/core_ext/string/access.rb +5 -16
  94. data/lib/active_support/core_ext/string/conversions.rb +3 -2
  95. data/lib/active_support/core_ext/string/filters.rb +42 -1
  96. data/lib/active_support/core_ext/string/inflections.rb +46 -7
  97. data/lib/active_support/core_ext/string/inquiry.rb +2 -1
  98. data/lib/active_support/core_ext/string/multibyte.rb +6 -5
  99. data/lib/active_support/core_ext/string/output_safety.rb +129 -20
  100. data/lib/active_support/core_ext/string/starts_ends_with.rb +2 -2
  101. data/lib/active_support/core_ext/string/strip.rb +3 -1
  102. data/lib/active_support/core_ext/symbol/starts_ends_with.rb +6 -0
  103. data/lib/active_support/core_ext/symbol.rb +3 -0
  104. data/lib/active_support/core_ext/time/calculations.rb +59 -10
  105. data/lib/active_support/core_ext/time/conversions.rb +15 -12
  106. data/lib/active_support/core_ext/time/deprecated_conversions.rb +22 -0
  107. data/lib/active_support/core_ext/time/zones.rb +7 -22
  108. data/lib/active_support/core_ext/time.rb +1 -0
  109. data/lib/active_support/core_ext/uri.rb +3 -22
  110. data/lib/active_support/core_ext.rb +2 -1
  111. data/lib/active_support/current_attributes/test_helper.rb +13 -0
  112. data/lib/active_support/current_attributes.rb +47 -16
  113. data/lib/active_support/dependencies/interlock.rb +10 -18
  114. data/lib/active_support/dependencies/require_dependency.rb +28 -0
  115. data/lib/active_support/dependencies.rb +60 -715
  116. data/lib/active_support/deprecation/behaviors.rb +21 -5
  117. data/lib/active_support/deprecation/disallowed.rb +56 -0
  118. data/lib/active_support/deprecation/instance_delegator.rb +0 -1
  119. data/lib/active_support/deprecation/method_wrappers.rb +18 -23
  120. data/lib/active_support/deprecation/proxy_wrappers.rb +31 -8
  121. data/lib/active_support/deprecation/reporting.rb +50 -7
  122. data/lib/active_support/deprecation.rb +7 -2
  123. data/lib/active_support/descendants_tracker.rb +190 -34
  124. data/lib/active_support/digest.rb +5 -3
  125. data/lib/active_support/duration/iso8601_parser.rb +5 -7
  126. data/lib/active_support/duration/iso8601_serializer.rb +27 -15
  127. data/lib/active_support/duration.rb +149 -67
  128. data/lib/active_support/encrypted_configuration.rb +12 -5
  129. data/lib/active_support/encrypted_file.rb +23 -5
  130. data/lib/active_support/environment_inquirer.rb +20 -0
  131. data/lib/active_support/error_reporter.rb +117 -0
  132. data/lib/active_support/evented_file_update_checker.rb +85 -122
  133. data/lib/active_support/execution_context/test_helper.rb +13 -0
  134. data/lib/active_support/execution_context.rb +53 -0
  135. data/lib/active_support/execution_wrapper.rb +44 -21
  136. data/lib/active_support/executor/test_helper.rb +7 -0
  137. data/lib/active_support/file_update_checker.rb +0 -1
  138. data/lib/active_support/fork_tracker.rb +71 -0
  139. data/lib/active_support/gem_version.rb +5 -5
  140. data/lib/active_support/hash_with_indifferent_access.rb +73 -43
  141. data/lib/active_support/html_safe_translation.rb +43 -0
  142. data/lib/active_support/i18n.rb +2 -0
  143. data/lib/active_support/i18n_railtie.rb +15 -8
  144. data/lib/active_support/inflector/inflections.rb +25 -14
  145. data/lib/active_support/inflector/methods.rb +38 -71
  146. data/lib/active_support/inflector/transliterate.rb +47 -18
  147. data/lib/active_support/isolated_execution_state.rb +72 -0
  148. data/lib/active_support/json/decoding.rb +25 -26
  149. data/lib/active_support/json/encoding.rb +14 -6
  150. data/lib/active_support/key_generator.rb +23 -38
  151. data/lib/active_support/lazy_load_hooks.rb +19 -5
  152. data/lib/active_support/locale/en.rb +33 -0
  153. data/lib/active_support/locale/en.yml +8 -4
  154. data/lib/active_support/log_subscriber/test_helper.rb +2 -2
  155. data/lib/active_support/log_subscriber.rb +51 -11
  156. data/lib/active_support/logger.rb +6 -22
  157. data/lib/active_support/logger_silence.rb +11 -19
  158. data/lib/active_support/logger_thread_safe_level.rb +45 -10
  159. data/lib/active_support/message_encryptor.rb +20 -19
  160. data/lib/active_support/message_verifier.rb +53 -21
  161. data/lib/active_support/messages/metadata.rb +13 -4
  162. data/lib/active_support/messages/rotation_configuration.rb +2 -1
  163. data/lib/active_support/messages/rotator.rb +10 -9
  164. data/lib/active_support/multibyte/chars.rb +17 -76
  165. data/lib/active_support/multibyte/unicode.rb +7 -331
  166. data/lib/active_support/multibyte.rb +1 -1
  167. data/lib/active_support/notifications/fanout.rb +163 -37
  168. data/lib/active_support/notifications/instrumenter.rb +90 -11
  169. data/lib/active_support/notifications.rb +88 -30
  170. data/lib/active_support/number_helper/number_converter.rb +6 -9
  171. data/lib/active_support/number_helper/number_to_currency_converter.rb +12 -12
  172. data/lib/active_support/number_helper/number_to_delimited_converter.rb +4 -3
  173. data/lib/active_support/number_helper/number_to_human_converter.rb +4 -3
  174. data/lib/active_support/number_helper/number_to_human_size_converter.rb +5 -4
  175. data/lib/active_support/number_helper/number_to_percentage_converter.rb +3 -1
  176. data/lib/active_support/number_helper/number_to_phone_converter.rb +3 -2
  177. data/lib/active_support/number_helper/number_to_rounded_converter.rb +12 -7
  178. data/lib/active_support/number_helper/rounding_helper.rb +12 -32
  179. data/lib/active_support/number_helper.rb +36 -12
  180. data/lib/active_support/option_merger.rb +15 -4
  181. data/lib/active_support/ordered_hash.rb +2 -2
  182. data/lib/active_support/ordered_options.rb +14 -4
  183. data/lib/active_support/parameter_filter.rb +138 -0
  184. data/lib/active_support/per_thread_registry.rb +6 -1
  185. data/lib/active_support/rails.rb +1 -10
  186. data/lib/active_support/railtie.rb +77 -5
  187. data/lib/active_support/reloader.rb +5 -6
  188. data/lib/active_support/rescuable.rb +8 -8
  189. data/lib/active_support/ruby_features.rb +7 -0
  190. data/lib/active_support/secure_compare_rotator.rb +51 -0
  191. data/lib/active_support/security_utils.rb +19 -12
  192. data/lib/active_support/string_inquirer.rb +2 -3
  193. data/lib/active_support/subscriber.rb +79 -46
  194. data/lib/active_support/tagged_logging.rb +58 -9
  195. data/lib/active_support/test_case.rb +79 -0
  196. data/lib/active_support/testing/assertions.rb +62 -11
  197. data/lib/active_support/testing/deprecation.rb +52 -2
  198. data/lib/active_support/testing/file_fixtures.rb +2 -0
  199. data/lib/active_support/testing/isolation.rb +4 -4
  200. data/lib/active_support/testing/method_call_assertions.rb +32 -5
  201. data/lib/active_support/testing/parallelization/server.rb +82 -0
  202. data/lib/active_support/testing/parallelization/worker.rb +103 -0
  203. data/lib/active_support/testing/parallelization.rb +55 -0
  204. data/lib/active_support/testing/parallelize_executor.rb +76 -0
  205. data/lib/active_support/testing/stream.rb +4 -7
  206. data/lib/active_support/testing/tagged_logging.rb +1 -1
  207. data/lib/active_support/testing/time_helpers.rb +60 -14
  208. data/lib/active_support/time_with_zone.rb +139 -64
  209. data/lib/active_support/values/time_zone.rb +66 -30
  210. data/lib/active_support/version.rb +1 -1
  211. data/lib/active_support/xml_mini/jdom.rb +3 -4
  212. data/lib/active_support/xml_mini/libxml.rb +7 -7
  213. data/lib/active_support/xml_mini/libxmlsax.rb +5 -5
  214. data/lib/active_support/xml_mini/nokogiri.rb +6 -6
  215. data/lib/active_support/xml_mini/nokogirisax.rb +4 -4
  216. data/lib/active_support/xml_mini/rexml.rb +11 -4
  217. data/lib/active_support/xml_mini.rb +7 -14
  218. data/lib/active_support.rb +30 -1
  219. metadata +64 -35
  220. data/lib/active_support/core_ext/array/prepend_and_append.rb +0 -9
  221. data/lib/active_support/core_ext/hash/compact.rb +0 -29
  222. data/lib/active_support/core_ext/hash/transform_values.rb +0 -32
  223. data/lib/active_support/core_ext/kernel/agnostics.rb +0 -13
  224. data/lib/active_support/core_ext/marshal.rb +0 -24
  225. data/lib/active_support/core_ext/module/reachable.rb +0 -11
  226. data/lib/active_support/core_ext/numeric/inquiry.rb +0 -28
  227. data/lib/active_support/core_ext/range/include_range.rb +0 -3
  228. data/lib/active_support/values/unicode_tables.dat +0 -0
@@ -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_fs(#{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_fs(#{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
@@ -1,64 +1,103 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ module ActiveSupport
4
+ module EnumerableCoreExt # :nodoc:
5
+ module Constants
6
+ private
7
+ def const_missing(name)
8
+ if name == :SoleItemExpectedError
9
+ ::ActiveSupport::EnumerableCoreExt::SoleItemExpectedError
10
+ else
11
+ super
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
17
+
3
18
  module Enumerable
19
+ # Error generated by +sole+ when called on an enumerable that doesn't have
20
+ # exactly one item.
21
+ class SoleItemExpectedError < StandardError; end
22
+
23
+ # HACK: For performance reasons, Enumerable shouldn't have any constants of its own.
24
+ # So we move SoleItemExpectedError into ActiveSupport::EnumerableCoreExt.
25
+ ActiveSupport::EnumerableCoreExt::SoleItemExpectedError = remove_const(:SoleItemExpectedError)
26
+ singleton_class.prepend(ActiveSupport::EnumerableCoreExt::Constants)
27
+
4
28
  # Enumerable#sum was added in Ruby 2.4, but it only works with Numeric elements
5
29
  # when we omit an identity.
30
+
31
+ # :stopdoc:
32
+
33
+ # We can't use Refinements here because Refinements with Module which will be prepended
34
+ # doesn't work well https://bugs.ruby-lang.org/issues/13446
35
+ alias :_original_sum_with_required_identity :sum
36
+ private :_original_sum_with_required_identity
37
+
38
+ # :startdoc:
39
+
40
+ # Calculates the minimum from the extracted elements.
6
41
  #
7
- # We tried shimming it to attempt the fast native method, rescue TypeError,
8
- # and fall back to the compatible implementation, but that's much slower than
9
- # just calling the compat method in the first place.
10
- if Enumerable.instance_methods(false).include?(:sum) && !((?a..?b).sum rescue false)
11
- # :stopdoc:
12
-
13
- # We can't use Refinements here because Refinements with Module which will be prepended
14
- # doesn't work well https://bugs.ruby-lang.org/issues/13446
15
- alias :_original_sum_with_required_identity :sum
16
- private :_original_sum_with_required_identity
17
-
18
- # :startdoc:
19
-
20
- # Calculates a sum from the elements.
21
- #
22
- # payments.sum { |p| p.price * p.tax_rate }
23
- # payments.sum(&:price)
24
- #
25
- # The latter is a shortcut for:
26
- #
27
- # payments.inject(0) { |sum, p| sum + p.price }
28
- #
29
- # It can also calculate the sum without the use of a block.
30
- #
31
- # [5, 15, 10].sum # => 30
32
- # ['foo', 'bar'].sum # => "foobar"
33
- # [[1, 2], [3, 1, 5]].sum # => [1, 2, 3, 1, 5]
34
- #
35
- # The default sum of an empty list is zero. You can override this default:
36
- #
37
- # [].sum(Payment.new(0)) { |i| i.amount } # => Payment.new(0)
38
- def sum(identity = nil, &block)
39
- if identity
40
- _original_sum_with_required_identity(identity, &block)
41
- elsif block_given?
42
- map(&block).sum(identity)
43
- else
44
- inject(:+) || 0
45
- end
46
- end
47
- else
48
- def sum(identity = nil, &block)
49
- if block_given?
50
- map(&block).sum(identity)
51
- else
52
- sum = identity ? inject(identity, :+) : inject(:+)
53
- sum || identity || 0
54
- end
42
+ # payments = [Payment.new(5), Payment.new(15), Payment.new(10)]
43
+ # payments.minimum(:price) # => 5
44
+ def minimum(key)
45
+ map(&key).min
46
+ end
47
+
48
+ # Calculates the maximum from the extracted elements.
49
+ #
50
+ # payments = [Payment.new(5), Payment.new(15), Payment.new(10)]
51
+ # payments.maximum(:price) # => 15
52
+ def maximum(key)
53
+ map(&key).max
54
+ end
55
+
56
+ # Calculates a sum from the elements.
57
+ #
58
+ # payments.sum { |p| p.price * p.tax_rate }
59
+ # payments.sum(&:price)
60
+ #
61
+ # The latter is a shortcut for:
62
+ #
63
+ # payments.inject(0) { |sum, p| sum + p.price }
64
+ #
65
+ # It can also calculate the sum without the use of a block.
66
+ #
67
+ # [5, 15, 10].sum # => 30
68
+ # ['foo', 'bar'].sum('') # => "foobar"
69
+ # [[1, 2], [3, 1, 5]].sum([]) # => [1, 2, 3, 1, 5]
70
+ #
71
+ # The default sum of an empty list is zero. You can override this default:
72
+ #
73
+ # [].sum(Payment.new(0)) { |i| i.amount } # => Payment.new(0)
74
+ def sum(identity = nil, &block)
75
+ if identity
76
+ _original_sum_with_required_identity(identity, &block)
77
+ elsif block_given?
78
+ map(&block).sum
79
+ # we check `first(1) == []` to check if we have an
80
+ # empty Enumerable; checking `empty?` would return
81
+ # true for `[nil]`, which we want to deprecate to
82
+ # keep consistent with Ruby
83
+ elsif first.is_a?(Numeric) || first(1) == []
84
+ identity ||= 0
85
+ _original_sum_with_required_identity(identity, &block)
86
+ else
87
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
88
+ Rails 7.0 has deprecated Enumerable.sum in favor of Ruby's native implementation available since 2.4.
89
+ Sum of non-numeric elements requires an initial argument.
90
+ MSG
91
+ inject(:+) || 0
55
92
  end
56
93
  end
57
94
 
58
- # Convert an enumerable to a hash.
95
+ # Convert an enumerable to a hash, using the block result as the key and the
96
+ # element as the value.
59
97
  #
60
98
  # people.index_by(&:login)
61
99
  # # => { "nextangle" => <Person ...>, "chade-" => <Person ...>, ...}
100
+ #
62
101
  # people.index_by { |person| "#{person.first_name} #{person.last_name}" }
63
102
  # # => { "Chade- Fowlersburg-e" => <Person ...>, "David Heinemeier Hansson" => <Person ...>, ...}
64
103
  def index_by
@@ -71,6 +110,33 @@ module Enumerable
71
110
  end
72
111
  end
73
112
 
113
+ # Convert an enumerable to a hash, using the element as the key and the block
114
+ # result as the value.
115
+ #
116
+ # post = Post.new(title: "hey there", body: "what's up?")
117
+ #
118
+ # %i( title body ).index_with { |attr_name| post.public_send(attr_name) }
119
+ # # => { title: "hey there", body: "what's up?" }
120
+ #
121
+ # If an argument is passed instead of a block, it will be used as the value
122
+ # for all elements:
123
+ #
124
+ # %i( created_at updated_at ).index_with(Time.now)
125
+ # # => { created_at: 2020-03-09 22:31:47, updated_at: 2020-03-09 22:31:47 }
126
+ def index_with(default = (no_default = true))
127
+ if block_given?
128
+ result = {}
129
+ each { |elem| result[elem] = yield(elem) }
130
+ result
131
+ elsif no_default
132
+ to_enum(:index_with) { size if respond_to?(:size) }
133
+ else
134
+ result = {}
135
+ each { |elem| result[elem] = default }
136
+ result
137
+ end
138
+ end
139
+
74
140
  # Returns +true+ if the enumerable has more than 1 element. Functionally
75
141
  # equivalent to <tt>enum.to_a.size > 1</tt>. Can be called with a block too,
76
142
  # much like any?, so <tt>people.many? { |p| p.age > 26 }</tt> returns +true+
@@ -87,24 +153,40 @@ module Enumerable
87
153
  end
88
154
  end
89
155
 
156
+ # Returns a new array that includes the passed elements.
157
+ #
158
+ # [ 1, 2, 3 ].including(4, 5)
159
+ # # => [ 1, 2, 3, 4, 5 ]
160
+ #
161
+ # ["David", "Rafael"].including %w[ Aaron Todd ]
162
+ # # => ["David", "Rafael", "Aaron", "Todd"]
163
+ def including(*elements)
164
+ to_a.including(*elements)
165
+ end
166
+
90
167
  # The negative of the <tt>Enumerable#include?</tt>. Returns +true+ if the
91
168
  # collection does not include the object.
92
169
  def exclude?(object)
93
170
  !include?(object)
94
171
  end
95
172
 
96
- # Returns a copy of the enumerable without the specified elements.
173
+ # Returns a copy of the enumerable excluding the specified elements.
97
174
  #
98
- # ["David", "Rafael", "Aaron", "Todd"].without "Aaron", "Todd"
175
+ # ["David", "Rafael", "Aaron", "Todd"].excluding "Aaron", "Todd"
99
176
  # # => ["David", "Rafael"]
100
177
  #
101
- # {foo: 1, bar: 2, baz: 3}.without :bar
178
+ # ["David", "Rafael", "Aaron", "Todd"].excluding %w[ Aaron Todd ]
179
+ # # => ["David", "Rafael"]
180
+ #
181
+ # {foo: 1, bar: 2, baz: 3}.excluding :bar
102
182
  # # => {foo: 1, baz: 3}
103
- def without(*elements)
183
+ def excluding(*elements)
184
+ elements.flatten!(1)
104
185
  reject { |element| elements.include?(element) }
105
186
  end
187
+ alias :without :excluding
106
188
 
107
- # Convert an enumerable to an array based on the given key.
189
+ # Extract the given key from each element in the enumerable.
108
190
  #
109
191
  # [{ name: "David" }, { name: "Rafael" }, { name: "Aaron" }].pluck(:name)
110
192
  # # => ["David", "Rafael", "Aaron"]
@@ -115,12 +197,91 @@ module Enumerable
115
197
  if keys.many?
116
198
  map { |element| keys.map { |key| element[key] } }
117
199
  else
118
- map { |element| element[keys.first] }
200
+ key = keys.first
201
+ map { |element| element[key] }
202
+ end
203
+ end
204
+
205
+ # Extract the given key from the first element in the enumerable.
206
+ #
207
+ # [{ name: "David" }, { name: "Rafael" }, { name: "Aaron" }].pick(:name)
208
+ # # => "David"
209
+ #
210
+ # [{ id: 1, name: "David" }, { id: 2, name: "Rafael" }].pick(:id, :name)
211
+ # # => [1, "David"]
212
+ def pick(*keys)
213
+ return if none?
214
+
215
+ if keys.many?
216
+ keys.map { |key| first[key] }
217
+ else
218
+ first[keys.first]
219
+ end
220
+ end
221
+
222
+ # Returns a new +Array+ without the blank items.
223
+ # Uses Object#blank? for determining if an item is blank.
224
+ #
225
+ # [1, "", nil, 2, " ", [], {}, false, true].compact_blank
226
+ # # => [1, 2, true]
227
+ #
228
+ # Set.new([nil, "", 1, 2])
229
+ # # => [2, 1] (or [1, 2])
230
+ #
231
+ # When called on a +Hash+, returns a new +Hash+ without the blank values.
232
+ #
233
+ # { a: "", b: 1, c: nil, d: [], e: false, f: true }.compact_blank
234
+ # # => { b: 1, f: true }
235
+ def compact_blank
236
+ reject(&:blank?)
237
+ end
238
+
239
+ # Returns a new +Array+ where the order has been set to that provided in the +series+, based on the +key+ of the
240
+ # objects in the original enumerable.
241
+ #
242
+ # [ Person.find(5), Person.find(3), Person.find(1) ].in_order_of(:id, [ 1, 5, 3 ])
243
+ # # => [ Person.find(1), Person.find(5), Person.find(3) ]
244
+ #
245
+ # If the +series+ include keys that have no corresponding element in the Enumerable, these are ignored.
246
+ # If the Enumerable has additional elements that aren't named in the +series+, these are not included in the result.
247
+ def in_order_of(key, series)
248
+ index_by(&key).values_at(*series).compact
249
+ end
250
+
251
+ # Returns the sole item in the enumerable. If there are no items, or more
252
+ # than one item, raises +Enumerable::SoleItemExpectedError+.
253
+ #
254
+ # ["x"].sole # => "x"
255
+ # Set.new.sole # => Enumerable::SoleItemExpectedError: no item found
256
+ # { a: 1, b: 2 }.sole # => Enumerable::SoleItemExpectedError: multiple items found
257
+ def sole
258
+ case count
259
+ when 1 then return first # rubocop:disable Style/RedundantReturn
260
+ when 0 then raise ActiveSupport::EnumerableCoreExt::SoleItemExpectedError, "no item found"
261
+ when 2.. then raise ActiveSupport::EnumerableCoreExt::SoleItemExpectedError, "multiple items found"
119
262
  end
120
263
  end
121
264
  end
122
265
 
123
- class Range #:nodoc:
266
+ class Hash
267
+ # Hash#reject has its own definition, so this needs one too.
268
+ def compact_blank # :nodoc:
269
+ reject { |_k, v| v.blank? }
270
+ end
271
+
272
+ # Removes all blank values from the +Hash+ in place and returns self.
273
+ # Uses Object#blank? for determining if a value is blank.
274
+ #
275
+ # h = { a: "", b: 1, c: nil, d: [], e: false, f: true }
276
+ # h.compact_blank!
277
+ # # => { b: 1, f: true }
278
+ def compact_blank!
279
+ # use delete_if rather than reject! because it always returns self even if nothing changed
280
+ delete_if { |_k, v| v.blank? }
281
+ end
282
+ end
283
+
284
+ class Range # :nodoc:
124
285
  # Optimize range sum to use arithmetic progression if a block is not given and
125
286
  # we have a range of numeric values.
126
287
  def sum(identity = nil)
@@ -138,27 +299,31 @@ class Range #:nodoc:
138
299
  end
139
300
  end
140
301
 
141
- # Array#sum was added in Ruby 2.4 but it only works with Numeric elements.
142
- #
143
- # We tried shimming it to attempt the fast native method, rescue TypeError,
144
- # and fall back to the compatible implementation, but that's much slower than
145
- # just calling the compat method in the first place.
146
- if Array.instance_methods(false).include?(:sum) && !(%w[a].sum rescue false)
147
- # Using Refinements here in order not to expose our internal method
148
- using Module.new {
149
- refine Array do
150
- alias :orig_sum :sum
151
- end
152
- }
302
+ # Using Refinements here in order not to expose our internal method
303
+ using Module.new {
304
+ refine Array do
305
+ alias :orig_sum :sum
306
+ end
307
+ }
153
308
 
154
- class Array
155
- def sum(init = nil, &block) #:nodoc:
156
- if init.is_a?(Numeric) || first.is_a?(Numeric)
157
- init ||= 0
158
- orig_sum(init, &block)
159
- else
160
- super
161
- end
309
+ class Array # :nodoc:
310
+ def sum(init = nil, &block)
311
+ if init.is_a?(Numeric) || first.is_a?(Numeric)
312
+ init ||= 0
313
+ orig_sum(init, &block)
314
+ else
315
+ super
162
316
  end
163
317
  end
318
+
319
+ # Removes all blank elements from the +Array+ in place and returns self.
320
+ # Uses Object#blank? for determining if an item is blank.
321
+ #
322
+ # a = [1, "", nil, 2, " ", [], {}, false, true]
323
+ # a.compact_blank!
324
+ # # => [1, 2, true]
325
+ def compact_blank!
326
+ # use delete_if rather than reject! because it always returns self even if nothing changed
327
+ delete_if(&:blank?)
328
+ end
164
329
  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,
@@ -64,6 +64,8 @@ class File
64
64
  file_name = join(dir, basename)
65
65
  FileUtils.touch(file_name)
66
66
  stat(file_name)
67
+ rescue Errno::ENOENT
68
+ file_name = nil
67
69
  ensure
68
70
  FileUtils.rm_f(file_name) if file_name
69
71
  end
@@ -1,10 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "active_support/xml_mini"
4
- require "active_support/time"
5
3
  require "active_support/core_ext/object/blank"
6
4
  require "active_support/core_ext/object/to_param"
7
5
  require "active_support/core_ext/object/to_query"
6
+ require "active_support/core_ext/object/try"
8
7
  require "active_support/core_ext/array/wrap"
9
8
  require "active_support/core_ext/hash/reverse_merge"
10
9
  require "active_support/core_ext/string/inflections"
@@ -73,7 +72,7 @@ class Hash
73
72
  # configure your own builder with the <tt>:builder</tt> option. The method also accepts
74
73
  # options like <tt>:dasherize</tt> and friends, they are forwarded to the builder.
75
74
  def to_xml(options = {})
76
- require "active_support/builder" unless defined?(Builder)
75
+ require "active_support/builder" unless defined?(Builder::XmlMarkup)
77
76
 
78
77
  options = options.dup
79
78
  options[:indent] ||= 2
@@ -208,7 +207,7 @@ module ActiveSupport
208
207
  elsif become_empty_string?(value)
209
208
  ""
210
209
  elsif become_hash?(value)
211
- xml_value = Hash[value.map { |k, v| [k, deep_to_h(v)] }]
210
+ xml_value = value.transform_values { |v| deep_to_h(v) }
212
211
 
213
212
  # Turn { files: { file: #<StringIO> } } into { files: #<StringIO> } so it is compatible with
214
213
  # how multipart uploaded files from HTML appear
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Hash
4
+ # Returns a new hash with all values converted by the block operation.
5
+ # This includes the values from the root hash and from all
6
+ # nested hashes and arrays.
7
+ #
8
+ # hash = { person: { name: 'Rob', age: '28' } }
9
+ #
10
+ # hash.deep_transform_values{ |value| value.to_s.upcase }
11
+ # # => {person: {name: "ROB", age: "28"}}
12
+ def deep_transform_values(&block)
13
+ _deep_transform_values_in_object(self, &block)
14
+ end
15
+
16
+ # Destructively converts all values by using the block operation.
17
+ # This includes the values from the root hash and from all
18
+ # nested hashes and arrays.
19
+ def deep_transform_values!(&block)
20
+ _deep_transform_values_in_object!(self, &block)
21
+ end
22
+
23
+ private
24
+ # Support methods for deep transforming nested hashes and arrays.
25
+ def _deep_transform_values_in_object(object, &block)
26
+ case object
27
+ when Hash
28
+ object.transform_values { |value| _deep_transform_values_in_object(value, &block) }
29
+ when Array
30
+ object.map { |e| _deep_transform_values_in_object(e, &block) }
31
+ else
32
+ yield(object)
33
+ end
34
+ end
35
+
36
+ def _deep_transform_values_in_object!(object, &block)
37
+ case object
38
+ when Hash
39
+ object.transform_values! { |value| _deep_transform_values_in_object!(value, &block) }
40
+ when Array
41
+ object.map! { |e| _deep_transform_values_in_object!(e, &block) }
42
+ else
43
+ yield(object)
44
+ end
45
+ end
46
+ end
@@ -10,8 +10,8 @@ class Hash
10
10
  # This is useful for limiting a set of parameters to everything but a few known toggles:
11
11
  # @person.update(params[:person].except(:admin))
12
12
  def except(*keys)
13
- dup.except!(*keys)
14
- end
13
+ slice(*self.keys - keys)
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 }
@@ -3,7 +3,7 @@
3
3
  require "active_support/hash_with_indifferent_access"
4
4
 
5
5
  class Hash
6
- # Returns an <tt>ActiveSupport::HashWithIndifferentAccess</tt> out of its receiver:
6
+ # Returns an ActiveSupport::HashWithIndifferentAccess out of its receiver:
7
7
  #
8
8
  # { a: 1 }.with_indifferent_access['a'] # => 1
9
9
  def with_indifferent_access
@@ -13,8 +13,8 @@ class Hash
13
13
  # Called when object is nested under an object that receives
14
14
  # #with_indifferent_access. This method will be called on the current object
15
15
  # by the enclosing object and is aliased to #with_indifferent_access by
16
- # default. Subclasses of Hash may overwrite this method to return +self+ if
17
- # converting to an <tt>ActiveSupport::HashWithIndifferentAccess</tt> would not be
16
+ # default. Subclasses of Hash may override this method to return +self+ if
17
+ # converting to an ActiveSupport::HashWithIndifferentAccess would not be
18
18
  # desirable.
19
19
  #
20
20
  # b = { b: 1 }
@@ -1,35 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class Hash
4
- # Returns a new hash with all keys converted using the +block+ operation.
5
- #
6
- # hash = { name: 'Rob', age: '28' }
7
- #
8
- # hash.transform_keys { |key| key.to_s.upcase } # => {"NAME"=>"Rob", "AGE"=>"28"}
9
- #
10
- # If you do not provide a +block+, it will return an Enumerator
11
- # for chaining with other methods:
12
- #
13
- # hash.transform_keys.with_index { |k, i| [k, i].join } # => {"name0"=>"Rob", "age1"=>"28"}
14
- def transform_keys
15
- return enum_for(:transform_keys) { size } unless block_given?
16
- result = {}
17
- each_key do |key|
18
- result[yield(key)] = self[key]
19
- end
20
- result
21
- end unless method_defined? :transform_keys
22
-
23
- # Destructively converts all keys using the +block+ operations.
24
- # Same as +transform_keys+ but modifies +self+.
25
- def transform_keys!
26
- return enum_for(:transform_keys!) { size } unless block_given?
27
- keys.each do |key|
28
- self[yield(key)] = delete(key)
29
- end
30
- self
31
- end unless method_defined? :transform_keys!
32
-
33
4
  # Returns a new hash with all keys converted to strings.
34
5
  #
35
6
  # hash = { name: 'Rob', age: '28' }
@@ -141,11 +112,11 @@ class Hash
141
112
  end
142
113
 
143
114
  private
144
- # support methods for deep transforming nested hashes and arrays
115
+ # Support methods for deep transforming nested hashes and arrays.
145
116
  def _deep_transform_keys_in_object(object, &block)
146
117
  case object
147
118
  when Hash
148
- object.each_with_object({}) do |(key, value), result|
119
+ object.each_with_object(self.class.new) do |(key, value), result|
149
120
  result[yield(key)] = _deep_transform_keys_in_object(value, &block)
150
121
  end
151
122
  when Array