activesupport 4.0.12 → 7.0.2.4

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 (295) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +249 -501
  3. data/MIT-LICENSE +2 -2
  4. data/README.rdoc +10 -5
  5. data/lib/active_support/actionable_error.rb +48 -0
  6. data/lib/active_support/all.rb +5 -3
  7. data/lib/active_support/array_inquirer.rb +48 -0
  8. data/lib/active_support/backtrace_cleaner.rb +41 -13
  9. data/lib/active_support/benchmarkable.rb +7 -15
  10. data/lib/active_support/builder.rb +3 -1
  11. data/lib/active_support/cache/file_store.rb +96 -74
  12. data/lib/active_support/cache/mem_cache_store.rb +211 -103
  13. data/lib/active_support/cache/memory_store.rb +90 -58
  14. data/lib/active_support/cache/null_store.rb +19 -7
  15. data/lib/active_support/cache/redis_cache_store.rb +468 -0
  16. data/lib/active_support/cache/strategy/local_cache.rb +86 -83
  17. data/lib/active_support/cache/strategy/local_cache_middleware.rb +45 -0
  18. data/lib/active_support/cache.rb +580 -241
  19. data/lib/active_support/callbacks.rb +812 -425
  20. data/lib/active_support/code_generator.rb +65 -0
  21. data/lib/active_support/concern.rb +103 -14
  22. data/lib/active_support/concurrency/load_interlock_aware_monitor.rb +33 -0
  23. data/lib/active_support/concurrency/share_lock.rb +226 -0
  24. data/lib/active_support/configurable.rb +21 -19
  25. data/lib/active_support/configuration_file.rb +51 -0
  26. data/lib/active_support/core_ext/array/access.rb +47 -1
  27. data/lib/active_support/core_ext/array/conversions.rb +35 -44
  28. data/lib/active_support/core_ext/array/deprecated_conversions.rb +25 -0
  29. data/lib/active_support/core_ext/array/extract.rb +21 -0
  30. data/lib/active_support/core_ext/array/extract_options.rb +2 -0
  31. data/lib/active_support/core_ext/array/grouping.rb +26 -16
  32. data/lib/active_support/core_ext/array/inquiry.rb +19 -0
  33. data/lib/active_support/core_ext/array/wrap.rb +7 -4
  34. data/lib/active_support/core_ext/array.rb +10 -7
  35. data/lib/active_support/core_ext/benchmark.rb +5 -3
  36. data/lib/active_support/core_ext/big_decimal/conversions.rb +9 -26
  37. data/lib/active_support/core_ext/big_decimal.rb +3 -1
  38. data/lib/active_support/core_ext/class/attribute.rb +52 -49
  39. data/lib/active_support/core_ext/class/attribute_accessors.rb +5 -169
  40. data/lib/active_support/core_ext/class/subclasses.rb +25 -26
  41. data/lib/active_support/core_ext/class.rb +4 -4
  42. data/lib/active_support/core_ext/date/acts_like.rb +3 -1
  43. data/lib/active_support/core_ext/date/blank.rb +14 -0
  44. data/lib/active_support/core_ext/date/calculations.rb +31 -18
  45. data/lib/active_support/core_ext/date/conversions.rb +43 -32
  46. data/lib/active_support/core_ext/date/deprecated_conversions.rb +26 -0
  47. data/lib/active_support/core_ext/date/zones.rb +5 -34
  48. data/lib/active_support/core_ext/date.rb +7 -4
  49. data/lib/active_support/core_ext/date_and_time/calculations.rb +198 -66
  50. data/lib/active_support/core_ext/date_and_time/compatibility.rb +31 -0
  51. data/lib/active_support/core_ext/date_and_time/zones.rb +40 -0
  52. data/lib/active_support/core_ext/date_time/acts_like.rb +4 -2
  53. data/lib/active_support/core_ext/date_time/blank.rb +14 -0
  54. data/lib/active_support/core_ext/date_time/calculations.rb +79 -38
  55. data/lib/active_support/core_ext/date_time/compatibility.rb +18 -0
  56. data/lib/active_support/core_ext/date_time/conversions.rb +31 -26
  57. data/lib/active_support/core_ext/date_time/deprecated_conversions.rb +22 -0
  58. data/lib/active_support/core_ext/date_time.rb +8 -4
  59. data/lib/active_support/core_ext/digest/uuid.rb +79 -0
  60. data/lib/active_support/core_ext/digest.rb +3 -0
  61. data/lib/active_support/core_ext/enumerable.rb +249 -17
  62. data/lib/active_support/core_ext/file/atomic.rb +41 -32
  63. data/lib/active_support/core_ext/file.rb +3 -1
  64. data/lib/active_support/core_ext/hash/conversions.rb +71 -49
  65. data/lib/active_support/core_ext/hash/deep_merge.rb +9 -13
  66. data/lib/active_support/core_ext/hash/deep_transform_values.rb +46 -0
  67. data/lib/active_support/core_ext/hash/except.rb +14 -5
  68. data/lib/active_support/core_ext/hash/indifferent_access.rb +5 -3
  69. data/lib/active_support/core_ext/hash/keys.rb +39 -56
  70. data/lib/active_support/core_ext/hash/reverse_merge.rb +5 -2
  71. data/lib/active_support/core_ext/hash/slice.rb +8 -23
  72. data/lib/active_support/core_ext/hash.rb +10 -8
  73. data/lib/active_support/core_ext/integer/inflections.rb +3 -1
  74. data/lib/active_support/core_ext/integer/multiple.rb +3 -1
  75. data/lib/active_support/core_ext/integer/time.rb +11 -33
  76. data/lib/active_support/core_ext/integer.rb +5 -3
  77. data/lib/active_support/core_ext/kernel/concern.rb +14 -0
  78. data/lib/active_support/core_ext/kernel/reporting.rb +9 -78
  79. data/lib/active_support/core_ext/kernel/singleton_class.rb +2 -0
  80. data/lib/active_support/core_ext/kernel.rb +5 -4
  81. data/lib/active_support/core_ext/load_error.rb +5 -21
  82. data/lib/active_support/core_ext/module/aliasing.rb +6 -44
  83. data/lib/active_support/core_ext/module/anonymous.rb +12 -1
  84. data/lib/active_support/core_ext/module/attr_internal.rb +8 -8
  85. data/lib/active_support/core_ext/module/attribute_accessors.rb +186 -44
  86. data/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb +157 -0
  87. data/lib/active_support/core_ext/module/concerning.rb +140 -0
  88. data/lib/active_support/core_ext/module/delegation.rb +172 -45
  89. data/lib/active_support/core_ext/module/deprecation.rb +3 -3
  90. data/lib/active_support/core_ext/module/introspection.rb +23 -38
  91. data/lib/active_support/core_ext/module/redefine_method.rb +40 -0
  92. data/lib/active_support/core_ext/module/remove_method.rb +8 -3
  93. data/lib/active_support/core_ext/module.rb +13 -10
  94. data/lib/active_support/core_ext/name_error.rb +45 -4
  95. data/lib/active_support/core_ext/numeric/bytes.rb +22 -0
  96. data/lib/active_support/core_ext/numeric/conversions.rb +135 -127
  97. data/lib/active_support/core_ext/numeric/deprecated_conversions.rb +60 -0
  98. data/lib/active_support/core_ext/numeric/time.rb +37 -50
  99. data/lib/active_support/core_ext/numeric.rb +6 -3
  100. data/lib/active_support/core_ext/object/acts_like.rb +41 -6
  101. data/lib/active_support/core_ext/object/blank.rb +70 -20
  102. data/lib/active_support/core_ext/object/conversions.rb +6 -4
  103. data/lib/active_support/core_ext/object/deep_dup.rb +19 -10
  104. data/lib/active_support/core_ext/object/duplicable.rb +17 -47
  105. data/lib/active_support/core_ext/object/inclusion.rb +18 -15
  106. data/lib/active_support/core_ext/object/instance_variables.rb +3 -1
  107. data/lib/active_support/core_ext/object/json.rb +244 -0
  108. data/lib/active_support/core_ext/object/to_param.rb +3 -1
  109. data/lib/active_support/core_ext/object/to_query.rb +21 -8
  110. data/lib/active_support/core_ext/object/try.rb +106 -26
  111. data/lib/active_support/core_ext/object/with_options.rb +64 -5
  112. data/lib/active_support/core_ext/object.rb +14 -12
  113. data/lib/active_support/core_ext/pathname/existence.rb +21 -0
  114. data/lib/active_support/core_ext/pathname.rb +3 -0
  115. data/lib/active_support/core_ext/range/compare_range.rb +57 -0
  116. data/lib/active_support/core_ext/range/conversions.rb +37 -15
  117. data/lib/active_support/core_ext/range/deprecated_conversions.rb +26 -0
  118. data/lib/active_support/core_ext/range/each.rb +18 -17
  119. data/lib/active_support/core_ext/range/include_time_with_zone.rb +7 -0
  120. data/lib/active_support/core_ext/range/overlaps.rb +2 -0
  121. data/lib/active_support/core_ext/range.rb +7 -4
  122. data/lib/active_support/core_ext/regexp.rb +10 -1
  123. data/lib/active_support/core_ext/securerandom.rb +45 -0
  124. data/lib/active_support/core_ext/string/access.rb +42 -51
  125. data/lib/active_support/core_ext/string/behavior.rb +3 -1
  126. data/lib/active_support/core_ext/string/conversions.rb +18 -13
  127. data/lib/active_support/core_ext/string/exclude.rb +5 -3
  128. data/lib/active_support/core_ext/string/filters.rb +97 -7
  129. data/lib/active_support/core_ext/string/indent.rb +6 -4
  130. data/lib/active_support/core_ext/string/inflections.rb +106 -25
  131. data/lib/active_support/core_ext/string/inquiry.rb +4 -1
  132. data/lib/active_support/core_ext/string/multibyte.rb +18 -9
  133. data/lib/active_support/core_ext/string/output_safety.rb +227 -54
  134. data/lib/active_support/core_ext/string/starts_ends_with.rb +4 -2
  135. data/lib/active_support/core_ext/string/strip.rb +6 -5
  136. data/lib/active_support/core_ext/string/zones.rb +4 -1
  137. data/lib/active_support/core_ext/string.rb +15 -13
  138. data/lib/active_support/core_ext/symbol/starts_ends_with.rb +6 -0
  139. data/lib/active_support/core_ext/symbol.rb +3 -0
  140. data/lib/active_support/core_ext/time/acts_like.rb +3 -1
  141. data/lib/active_support/core_ext/time/calculations.rb +178 -116
  142. data/lib/active_support/core_ext/time/compatibility.rb +16 -0
  143. data/lib/active_support/core_ext/time/conversions.rb +37 -25
  144. data/lib/active_support/core_ext/time/deprecated_conversions.rb +22 -0
  145. data/lib/active_support/core_ext/time/zones.rb +44 -42
  146. data/lib/active_support/core_ext/time.rb +8 -5
  147. data/lib/active_support/core_ext/uri.rb +4 -25
  148. data/lib/active_support/core_ext.rb +4 -2
  149. data/lib/active_support/current_attributes/test_helper.rb +13 -0
  150. data/lib/active_support/current_attributes.rb +226 -0
  151. data/lib/active_support/dependencies/autoload.rb +3 -1
  152. data/lib/active_support/dependencies/interlock.rb +49 -0
  153. data/lib/active_support/dependencies/require_dependency.rb +28 -0
  154. data/lib/active_support/dependencies.rb +71 -696
  155. data/lib/active_support/deprecation/behaviors.rb +65 -16
  156. data/lib/active_support/deprecation/constant_accessor.rb +52 -0
  157. data/lib/active_support/deprecation/disallowed.rb +56 -0
  158. data/lib/active_support/deprecation/instance_delegator.rb +16 -2
  159. data/lib/active_support/deprecation/method_wrappers.rb +62 -21
  160. data/lib/active_support/deprecation/proxy_wrappers.rb +82 -31
  161. data/lib/active_support/deprecation/reporting.rb +81 -18
  162. data/lib/active_support/deprecation.rb +19 -11
  163. data/lib/active_support/descendants_tracker.rb +192 -34
  164. data/lib/active_support/digest.rb +22 -0
  165. data/lib/active_support/duration/iso8601_parser.rb +123 -0
  166. data/lib/active_support/duration/iso8601_serializer.rb +67 -0
  167. data/lib/active_support/duration.rb +437 -39
  168. data/lib/active_support/encrypted_configuration.rb +56 -0
  169. data/lib/active_support/encrypted_file.rb +117 -0
  170. data/lib/active_support/environment_inquirer.rb +20 -0
  171. data/lib/active_support/error_reporter.rb +117 -0
  172. data/lib/active_support/evented_file_update_checker.rb +170 -0
  173. data/lib/active_support/execution_context/test_helper.rb +13 -0
  174. data/lib/active_support/execution_context.rb +53 -0
  175. data/lib/active_support/execution_wrapper.rb +151 -0
  176. data/lib/active_support/executor/test_helper.rb +7 -0
  177. data/lib/active_support/executor.rb +8 -0
  178. data/lib/active_support/file_update_checker.rb +62 -37
  179. data/lib/active_support/fork_tracker.rb +71 -0
  180. data/lib/active_support/gem_version.rb +17 -0
  181. data/lib/active_support/gzip.rb +7 -5
  182. data/lib/active_support/hash_with_indifferent_access.rb +207 -54
  183. data/lib/active_support/html_safe_translation.rb +43 -0
  184. data/lib/active_support/i18n.rb +10 -6
  185. data/lib/active_support/i18n_railtie.rb +48 -19
  186. data/lib/active_support/inflections.rb +19 -12
  187. data/lib/active_support/inflector/inflections.rb +97 -37
  188. data/lib/active_support/inflector/methods.rb +192 -157
  189. data/lib/active_support/inflector/transliterate.rb +83 -33
  190. data/lib/active_support/inflector.rb +7 -5
  191. data/lib/active_support/isolated_execution_state.rb +64 -0
  192. data/lib/active_support/json/decoding.rb +37 -42
  193. data/lib/active_support/json/encoding.rb +93 -293
  194. data/lib/active_support/json.rb +4 -2
  195. data/lib/active_support/key_generator.rb +30 -47
  196. data/lib/active_support/lazy_load_hooks.rb +54 -21
  197. data/lib/active_support/locale/en.rb +33 -0
  198. data/lib/active_support/locale/en.yml +10 -4
  199. data/lib/active_support/log_subscriber/test_helper.rb +14 -12
  200. data/lib/active_support/log_subscriber.rb +61 -18
  201. data/lib/active_support/logger.rb +40 -4
  202. data/lib/active_support/logger_silence.rb +17 -20
  203. data/lib/active_support/logger_thread_safe_level.rb +69 -0
  204. data/lib/active_support/message_encryptor.rb +178 -55
  205. data/lib/active_support/message_verifier.rb +195 -26
  206. data/lib/active_support/messages/metadata.rb +80 -0
  207. data/lib/active_support/messages/rotation_configuration.rb +23 -0
  208. data/lib/active_support/messages/rotator.rb +57 -0
  209. data/lib/active_support/multibyte/chars.rb +45 -92
  210. data/lib/active_support/multibyte/unicode.rb +44 -377
  211. data/lib/active_support/multibyte.rb +5 -3
  212. data/lib/active_support/notifications/fanout.rb +177 -44
  213. data/lib/active_support/notifications/instrumenter.rb +117 -17
  214. data/lib/active_support/notifications.rb +106 -39
  215. data/lib/active_support/number_helper/number_converter.rb +181 -0
  216. data/lib/active_support/number_helper/number_to_currency_converter.rb +46 -0
  217. data/lib/active_support/number_helper/number_to_delimited_converter.rb +30 -0
  218. data/lib/active_support/number_helper/number_to_human_converter.rb +69 -0
  219. data/lib/active_support/number_helper/number_to_human_size_converter.rb +60 -0
  220. data/lib/active_support/number_helper/number_to_percentage_converter.rb +16 -0
  221. data/lib/active_support/number_helper/number_to_phone_converter.rb +59 -0
  222. data/lib/active_support/number_helper/number_to_rounded_converter.rb +59 -0
  223. data/lib/active_support/number_helper/rounding_helper.rb +46 -0
  224. data/lib/active_support/number_helper.rb +152 -394
  225. data/lib/active_support/option_merger.rb +18 -5
  226. data/lib/active_support/ordered_hash.rb +8 -6
  227. data/lib/active_support/ordered_options.rb +43 -7
  228. data/lib/active_support/parameter_filter.rb +138 -0
  229. data/lib/active_support/per_thread_registry.rb +24 -11
  230. data/lib/active_support/proxy_object.rb +2 -0
  231. data/lib/active_support/rails.rb +10 -11
  232. data/lib/active_support/railtie.rb +118 -12
  233. data/lib/active_support/reloader.rb +130 -0
  234. data/lib/active_support/rescuable.rb +112 -57
  235. data/lib/active_support/ruby_features.rb +7 -0
  236. data/lib/active_support/secure_compare_rotator.rb +51 -0
  237. data/lib/active_support/security_utils.rb +38 -0
  238. data/lib/active_support/string_inquirer.rb +11 -4
  239. data/lib/active_support/subscriber.rb +109 -39
  240. data/lib/active_support/tagged_logging.rb +54 -17
  241. data/lib/active_support/test_case.rb +121 -37
  242. data/lib/active_support/testing/assertions.rb +177 -39
  243. data/lib/active_support/testing/autorun.rb +5 -3
  244. data/lib/active_support/testing/constant_lookup.rb +3 -6
  245. data/lib/active_support/testing/declarative.rb +10 -22
  246. data/lib/active_support/testing/deprecation.rb +65 -11
  247. data/lib/active_support/testing/file_fixtures.rb +38 -0
  248. data/lib/active_support/testing/isolation.rb +56 -87
  249. data/lib/active_support/testing/method_call_assertions.rb +70 -0
  250. data/lib/active_support/testing/parallelization/server.rb +82 -0
  251. data/lib/active_support/testing/parallelization/worker.rb +103 -0
  252. data/lib/active_support/testing/parallelization.rb +55 -0
  253. data/lib/active_support/testing/parallelize_executor.rb +76 -0
  254. data/lib/active_support/testing/setup_and_teardown.rb +30 -10
  255. data/lib/active_support/testing/stream.rb +41 -0
  256. data/lib/active_support/testing/tagged_logging.rb +6 -4
  257. data/lib/active_support/testing/time_helpers.rb +246 -0
  258. data/lib/active_support/time.rb +13 -13
  259. data/lib/active_support/time_with_zone.rb +315 -90
  260. data/lib/active_support/values/time_zone.rb +306 -135
  261. data/lib/active_support/version.rb +6 -7
  262. data/lib/active_support/xml_mini/jdom.rb +117 -115
  263. data/lib/active_support/xml_mini/libxml.rb +22 -21
  264. data/lib/active_support/xml_mini/libxmlsax.rb +17 -19
  265. data/lib/active_support/xml_mini/nokogiri.rb +19 -19
  266. data/lib/active_support/xml_mini/nokogirisax.rb +16 -17
  267. data/lib/active_support/xml_mini/rexml.rb +25 -17
  268. data/lib/active_support/xml_mini.rb +67 -56
  269. data/lib/active_support.rb +58 -3
  270. metadata +125 -66
  271. data/lib/active_support/basic_object.rb +0 -11
  272. data/lib/active_support/buffered_logger.rb +0 -21
  273. data/lib/active_support/concurrency/latch.rb +0 -27
  274. data/lib/active_support/core_ext/array/prepend_and_append.rb +0 -7
  275. data/lib/active_support/core_ext/array/uniq_by.rb +0 -19
  276. data/lib/active_support/core_ext/class/delegating_attributes.rb +0 -40
  277. data/lib/active_support/core_ext/date_time/zones.rb +0 -24
  278. data/lib/active_support/core_ext/hash/diff.rb +0 -14
  279. data/lib/active_support/core_ext/kernel/agnostics.rb +0 -11
  280. data/lib/active_support/core_ext/kernel/debugger.rb +0 -10
  281. data/lib/active_support/core_ext/logger.rb +0 -67
  282. data/lib/active_support/core_ext/marshal.rb +0 -21
  283. data/lib/active_support/core_ext/module/qualified_const.rb +0 -52
  284. data/lib/active_support/core_ext/module/reachable.rb +0 -8
  285. data/lib/active_support/core_ext/object/to_json.rb +0 -27
  286. data/lib/active_support/core_ext/proc.rb +0 -17
  287. data/lib/active_support/core_ext/range/include_range.rb +0 -23
  288. data/lib/active_support/core_ext/string/encoding.rb +0 -8
  289. data/lib/active_support/core_ext/struct.rb +0 -6
  290. data/lib/active_support/core_ext/thread.rb +0 -79
  291. data/lib/active_support/core_ext/time/marshal.rb +0 -30
  292. data/lib/active_support/file_watcher.rb +0 -36
  293. data/lib/active_support/json/variable.rb +0 -18
  294. data/lib/active_support/testing/pending.rb +0 -14
  295. data/lib/active_support/values/unicode_tables.dat +0 -0
@@ -1,4 +1,41 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Enumerable
4
+ INDEX_WITH_DEFAULT = Object.new
5
+ private_constant :INDEX_WITH_DEFAULT
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
+
11
+ # Enumerable#sum was added in Ruby 2.4, but it only works with Numeric elements
12
+ # when we omit an identity.
13
+
14
+ # :stopdoc:
15
+
16
+ # We can't use Refinements here because Refinements with Module which will be prepended
17
+ # doesn't work well https://bugs.ruby-lang.org/issues/13446
18
+ alias :_original_sum_with_required_identity :sum
19
+ private :_original_sum_with_required_identity
20
+
21
+ # :startdoc:
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
+
2
39
  # Calculates a sum from the elements.
3
40
  #
4
41
  # payments.sum { |p| p.price * p.tax_rate }
@@ -10,32 +47,76 @@ module Enumerable
10
47
  #
11
48
  # It can also calculate the sum without the use of a block.
12
49
  #
13
- # [5, 15, 10].sum # => 30
14
- # ['foo', 'bar'].sum # => "foobar"
15
- # [[1, 2], [3, 1, 5]].sum => [1, 2, 3, 1, 5]
50
+ # [5, 15, 10].sum # => 30
51
+ # ['foo', 'bar'].sum('') # => "foobar"
52
+ # [[1, 2], [3, 1, 5]].sum([]) # => [1, 2, 3, 1, 5]
16
53
  #
17
54
  # The default sum of an empty list is zero. You can override this default:
18
55
  #
19
- # [].sum(Payment.new(0)) { |i| i.amount } # => Payment.new(0)
20
- def sum(identity = 0, &block)
21
- if block_given?
22
- map(&block).sum(identity)
56
+ # [].sum(Payment.new(0)) { |i| i.amount } # => Payment.new(0)
57
+ def sum(identity = nil, &block)
58
+ if identity
59
+ _original_sum_with_required_identity(identity, &block)
60
+ elsif block_given?
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)
23
69
  else
24
- inject { |sum, element| sum + element } || identity
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
74
+ inject(:+) || 0
25
75
  end
26
76
  end
27
77
 
28
- # Convert an enumerable to a hash.
78
+ # Convert an enumerable to a hash, using the block result as the key and the
79
+ # element as the value.
29
80
  #
30
81
  # people.index_by(&:login)
31
- # => { "nextangle" => <Person ...>, "chade-" => <Person ...>, ...}
82
+ # # => { "nextangle" => <Person ...>, "chade-" => <Person ...>, ...}
83
+ #
32
84
  # people.index_by { |person| "#{person.first_name} #{person.last_name}" }
33
- # => { "Chade- Fowlersburg-e" => <Person ...>, "David Heinemeier Hansson" => <Person ...>, ...}
85
+ # # => { "Chade- Fowlersburg-e" => <Person ...>, "David Heinemeier Hansson" => <Person ...>, ...}
34
86
  def index_by
35
87
  if block_given?
36
- Hash[map { |elem| [yield(elem), elem] }]
88
+ result = {}
89
+ each { |elem| result[yield(elem)] = elem }
90
+ result
37
91
  else
38
- to_enum :index_by
92
+ to_enum(:index_by) { size if respond_to?(:size) }
93
+ end
94
+ end
95
+
96
+ # Convert an enumerable to a hash, using the element as the key and the block
97
+ # result as the value.
98
+ #
99
+ # post = Post.new(title: "hey there", body: "what's up?")
100
+ #
101
+ # %i( title body ).index_with { |attr_name| post.public_send(attr_name) }
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 }
109
+ def index_with(default = INDEX_WITH_DEFAULT)
110
+ if block_given?
111
+ result = {}
112
+ each { |elem| result[elem] = yield(elem) }
113
+ result
114
+ elsif default != INDEX_WITH_DEFAULT
115
+ result = {}
116
+ each { |elem| result[elem] = default }
117
+ result
118
+ else
119
+ to_enum(:index_with) { size if respond_to?(:size) }
39
120
  end
40
121
  end
41
122
 
@@ -55,26 +136,177 @@ module Enumerable
55
136
  end
56
137
  end
57
138
 
139
+ # Returns a new array that includes the passed elements.
140
+ #
141
+ # [ 1, 2, 3 ].including(4, 5)
142
+ # # => [ 1, 2, 3, 4, 5 ]
143
+ #
144
+ # ["David", "Rafael"].including %w[ Aaron Todd ]
145
+ # # => ["David", "Rafael", "Aaron", "Todd"]
146
+ def including(*elements)
147
+ to_a.including(*elements)
148
+ end
149
+
58
150
  # The negative of the <tt>Enumerable#include?</tt>. Returns +true+ if the
59
151
  # collection does not include the object.
60
152
  def exclude?(object)
61
153
  !include?(object)
62
154
  end
155
+
156
+ # Returns a copy of the enumerable excluding the specified elements.
157
+ #
158
+ # ["David", "Rafael", "Aaron", "Todd"].excluding "Aaron", "Todd"
159
+ # # => ["David", "Rafael"]
160
+ #
161
+ # ["David", "Rafael", "Aaron", "Todd"].excluding %w[ Aaron Todd ]
162
+ # # => ["David", "Rafael"]
163
+ #
164
+ # {foo: 1, bar: 2, baz: 3}.excluding :bar
165
+ # # => {foo: 1, baz: 3}
166
+ def excluding(*elements)
167
+ elements.flatten!(1)
168
+ reject { |element| elements.include?(element) }
169
+ end
170
+ alias :without :excluding
171
+
172
+ # Extract the given key from each element in the enumerable.
173
+ #
174
+ # [{ name: "David" }, { name: "Rafael" }, { name: "Aaron" }].pluck(:name)
175
+ # # => ["David", "Rafael", "Aaron"]
176
+ #
177
+ # [{ id: 1, name: "David" }, { id: 2, name: "Rafael" }].pluck(:id, :name)
178
+ # # => [[1, "David"], [2, "Rafael"]]
179
+ def pluck(*keys)
180
+ if keys.many?
181
+ map { |element| keys.map { |key| element[key] } }
182
+ else
183
+ key = keys.first
184
+ map { |element| element[key] }
185
+ end
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
63
265
  end
64
266
 
65
- class Range #:nodoc:
267
+ class Range # :nodoc:
66
268
  # Optimize range sum to use arithmetic progression if a block is not given and
67
269
  # we have a range of numeric values.
68
- def sum(identity = 0)
270
+ def sum(identity = nil)
69
271
  if block_given? || !(first.is_a?(Integer) && last.is_a?(Integer))
70
272
  super
71
273
  else
72
274
  actual_last = exclude_end? ? (last - 1) : last
73
275
  if actual_last >= first
74
- (actual_last - first + 1) * (actual_last + first) / 2
276
+ sum = identity || 0
277
+ sum + (actual_last - first + 1) * (actual_last + first) / 2
75
278
  else
76
- identity
279
+ identity || 0
77
280
  end
78
281
  end
79
282
  end
80
283
  end
284
+
285
+ # Using Refinements here in order not to expose our internal method
286
+ using Module.new {
287
+ refine Array do
288
+ alias :orig_sum :sum
289
+ end
290
+ }
291
+
292
+ class Array # :nodoc:
293
+ def sum(init = nil, &block)
294
+ if init.is_a?(Numeric) || first.is_a?(Numeric)
295
+ init ||= 0
296
+ orig_sum(init, &block)
297
+ else
298
+ super
299
+ end
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
312
+ end
@@ -1,4 +1,6 @@
1
- require 'fileutils'
1
+ # frozen_string_literal: true
2
+
3
+ require "fileutils"
2
4
 
3
5
  class File
4
6
  # Write to a file atomically. Useful for situations where you don't
@@ -8,55 +10,62 @@ class File
8
10
  # file.write('hello')
9
11
  # end
10
12
  #
11
- # If your temp directory is not on the same filesystem as the file you're
12
- # trying to write, you can provide a different temporary directory.
13
+ # This method needs to create a temporary file. By default it will create it
14
+ # in the same directory as the destination file. If you don't like this
15
+ # behavior you can provide a different directory but it must be on the
16
+ # same physical filesystem as the file you're trying to write.
13
17
  #
14
18
  # File.atomic_write('/data/something.important', '/data/tmp') do |file|
15
19
  # file.write('hello')
16
20
  # end
17
- def self.atomic_write(file_name, temp_dir = Dir.tmpdir)
18
- require 'tempfile' unless defined?(Tempfile)
19
- require 'fileutils' unless defined?(FileUtils)
20
-
21
- temp_file = Tempfile.new(basename(file_name), temp_dir)
22
- temp_file.binmode
23
- yield temp_file
24
- temp_file.close
25
-
26
- if File.exist?(file_name)
27
- # Get original file permissions
28
- old_stat = stat(file_name)
29
- else
30
- # If not possible, probe which are the default permissions in the
31
- # destination directory.
32
- old_stat = probe_stat_in(dirname(file_name))
33
- end
21
+ def self.atomic_write(file_name, temp_dir = dirname(file_name))
22
+ require "tempfile" unless defined?(Tempfile)
23
+
24
+ Tempfile.open(".#{basename(file_name)}", temp_dir) do |temp_file|
25
+ temp_file.binmode
26
+ return_val = yield temp_file
27
+ temp_file.close
28
+
29
+ old_stat = if exist?(file_name)
30
+ # Get original file permissions
31
+ stat(file_name)
32
+ else
33
+ # If not possible, probe which are the default permissions in the
34
+ # destination directory.
35
+ probe_stat_in(dirname(file_name))
36
+ end
34
37
 
35
- # Overwrite original file with temp file
36
- FileUtils.mv(temp_file.path, file_name)
38
+ if old_stat
39
+ # Set correct permissions on new file
40
+ begin
41
+ chown(old_stat.uid, old_stat.gid, temp_file.path)
42
+ # This operation will affect filesystem ACL's
43
+ chmod(old_stat.mode, temp_file.path)
44
+ rescue Errno::EPERM, Errno::EACCES
45
+ # Changing file ownership failed, moving on.
46
+ end
47
+ end
37
48
 
38
- # Set correct permissions on new file
39
- begin
40
- chown(old_stat.uid, old_stat.gid, file_name)
41
- # This operation will affect filesystem ACL's
42
- chmod(old_stat.mode, file_name)
43
- rescue Errno::EPERM
44
- # Changing file ownership failed, moving on.
49
+ # Overwrite original file with temp file
50
+ rename(temp_file.path, file_name)
51
+ return_val
45
52
  end
46
53
  end
47
54
 
48
55
  # Private utility method.
49
- def self.probe_stat_in(dir) #:nodoc:
56
+ def self.probe_stat_in(dir) # :nodoc:
50
57
  basename = [
51
- '.permissions_check',
58
+ ".permissions_check",
52
59
  Thread.current.object_id,
53
60
  Process.pid,
54
61
  rand(1000000)
55
- ].join('.')
62
+ ].join(".")
56
63
 
57
64
  file_name = join(dir, basename)
58
65
  FileUtils.touch(file_name)
59
66
  stat(file_name)
67
+ rescue Errno::ENOENT
68
+ file_name = nil
60
69
  ensure
61
70
  FileUtils.rm_f(file_name) if file_name
62
71
  end
@@ -1 +1,3 @@
1
- require 'active_support/core_ext/file/atomic'
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/core_ext/file/atomic"
@@ -1,16 +1,18 @@
1
- require 'active_support/xml_mini'
2
- require 'active_support/time'
3
- require 'active_support/core_ext/object/blank'
4
- require 'active_support/core_ext/object/to_param'
5
- require 'active_support/core_ext/object/to_query'
6
- require 'active_support/core_ext/array/wrap'
7
- require 'active_support/core_ext/hash/reverse_merge'
8
- require 'active_support/core_ext/string/inflections'
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/xml_mini"
4
+ require "active_support/core_ext/object/blank"
5
+ require "active_support/core_ext/object/to_param"
6
+ require "active_support/core_ext/object/to_query"
7
+ require "active_support/core_ext/object/try"
8
+ require "active_support/core_ext/array/wrap"
9
+ require "active_support/core_ext/hash/reverse_merge"
10
+ require "active_support/core_ext/string/inflections"
9
11
 
10
12
  class Hash
11
13
  # Returns a string containing an XML representation of its receiver:
12
14
  #
13
- # {'foo' => 1, 'bar' => 2}.to_xml
15
+ # { foo: 1, bar: 2 }.to_xml
14
16
  # # =>
15
17
  # # <?xml version="1.0" encoding="UTF-8"?>
16
18
  # # <hash>
@@ -31,7 +33,7 @@ class Hash
31
33
  # with +key+ as <tt>:root</tt>, and +key+ singularized as second argument. The
32
34
  # callable can add nodes by using <tt>options[:builder]</tt>.
33
35
  #
34
- # 'foo'.to_xml(lambda { |options, key| options[:builder].b(key) })
36
+ # {foo: lambda { |options, key| options[:builder].b(key) }}.to_xml
35
37
  # # => "<b>foo</b>"
36
38
  #
37
39
  # * If +value+ responds to +to_xml+ the method is invoked with +key+ as <tt>:root</tt>.
@@ -43,7 +45,10 @@ class Hash
43
45
  # end
44
46
  #
45
47
  # { foo: Foo.new }.to_xml(skip_instruct: true)
46
- # # => "<hash><bar>fooing!</bar></hash>"
48
+ # # =>
49
+ # # <hash>
50
+ # # <bar>fooing!</bar>
51
+ # # </hash>
47
52
  #
48
53
  # * Otherwise, a node with +key+ as tag is created with a string representation of
49
54
  # +value+ as text node. If +value+ is +nil+ an attribute "nil" set to "true" is added.
@@ -52,8 +57,7 @@ class Hash
52
57
  #
53
58
  # XML_TYPE_NAMES = {
54
59
  # "Symbol" => "symbol",
55
- # "Fixnum" => "integer",
56
- # "Bignum" => "integer",
60
+ # "Integer" => "integer",
57
61
  # "BigDecimal" => "decimal",
58
62
  # "Float" => "float",
59
63
  # "TrueClass" => "boolean",
@@ -69,11 +73,11 @@ class Hash
69
73
  # configure your own builder with the <tt>:builder</tt> option. The method also accepts
70
74
  # options like <tt>:dasherize</tt> and friends, they are forwarded to the builder.
71
75
  def to_xml(options = {})
72
- require 'active_support/builder' unless defined?(Builder)
76
+ require "active_support/builder" unless defined?(Builder::XmlMarkup)
73
77
 
74
78
  options = options.dup
75
79
  options[:indent] ||= 2
76
- options[:root] ||= 'hash'
80
+ options[:root] ||= "hash"
77
81
  options[:builder] ||= Builder::XmlMarkup.new(indent: options[:indent])
78
82
 
79
83
  builder = options[:builder]
@@ -102,8 +106,26 @@ class Hash
102
106
  # hash = Hash.from_xml(xml)
103
107
  # # => {"hash"=>{"foo"=>1, "bar"=>2}}
104
108
  #
105
- # DisallowedType is raise if the XML contains attributes with <tt>type="yaml"</tt> or
106
- # <tt>type="symbol"</tt>. Use <tt>Hash.from_trusted_xml</tt> to parse this XML.
109
+ # +DisallowedType+ is raised if the XML contains attributes with <tt>type="yaml"</tt> or
110
+ # <tt>type="symbol"</tt>. Use <tt>Hash.from_trusted_xml</tt> to
111
+ # parse this XML.
112
+ #
113
+ # Custom +disallowed_types+ can also be passed in the form of an
114
+ # array.
115
+ #
116
+ # xml = <<-XML
117
+ # <?xml version="1.0" encoding="UTF-8"?>
118
+ # <hash>
119
+ # <foo type="integer">1</foo>
120
+ # <bar type="string">"David"</bar>
121
+ # </hash>
122
+ # XML
123
+ #
124
+ # hash = Hash.from_xml(xml, ['integer'])
125
+ # # => ActiveSupport::XMLConverter::DisallowedType: Disallowed type attribute: "integer"
126
+ #
127
+ # Note that passing custom disallowed types will override the default types,
128
+ # which are Symbol and YAML.
107
129
  def from_xml(xml, disallowed_types = nil)
108
130
  ActiveSupport::XMLConverter.new(xml, disallowed_types).to_h
109
131
  end
@@ -117,6 +139,8 @@ end
117
139
 
118
140
  module ActiveSupport
119
141
  class XMLConverter # :nodoc:
142
+ # Raised if the XML contains attributes with type="yaml" or
143
+ # type="symbol". Read Hash#from_xml for more details.
120
144
  class DisallowedType < StandardError
121
145
  def initialize(type)
122
146
  super "Disallowed type attribute: #{type.inspect}"
@@ -137,36 +161,36 @@ module ActiveSupport
137
161
  private
138
162
  def normalize_keys(params)
139
163
  case params
140
- when Hash
141
- Hash[params.map { |k,v| [k.to_s.tr('-', '_'), normalize_keys(v)] } ]
142
- when Array
143
- params.map { |v| normalize_keys(v) }
144
- else
145
- params
164
+ when Hash
165
+ Hash[params.map { |k, v| [k.to_s.tr("-", "_"), normalize_keys(v)] } ]
166
+ when Array
167
+ params.map { |v| normalize_keys(v) }
168
+ else
169
+ params
146
170
  end
147
171
  end
148
172
 
149
173
  def deep_to_h(value)
150
174
  case value
151
- when Hash
152
- process_hash(value)
153
- when Array
154
- process_array(value)
155
- when String
156
- value
157
- else
158
- raise "can't typecast #{value.class.name} - #{value.inspect}"
175
+ when Hash
176
+ process_hash(value)
177
+ when Array
178
+ process_array(value)
179
+ when String
180
+ value
181
+ else
182
+ raise "can't typecast #{value.class.name} - #{value.inspect}"
159
183
  end
160
184
  end
161
185
 
162
186
  def process_hash(value)
163
- if value.include?('type') && !value['type'].is_a?(Hash) && @disallowed_types.include?(value['type'])
164
- raise DisallowedType, value['type']
187
+ if value.include?("type") && !value["type"].is_a?(Hash) && @disallowed_types.include?(value["type"])
188
+ raise DisallowedType, value["type"]
165
189
  end
166
190
 
167
191
  if become_array?(value)
168
- _, entries = Array.wrap(value.detect { |k,v| not v.is_a?(String) })
169
- if entries.nil? || value['__content__'].try(:empty?)
192
+ _, entries = Array.wrap(value.detect { |k, v| not v.is_a?(String) })
193
+ if entries.nil? || value["__content__"].try(:empty?)
170
194
  []
171
195
  else
172
196
  case entries
@@ -182,28 +206,28 @@ module ActiveSupport
182
206
  process_content(value)
183
207
 
184
208
  elsif become_empty_string?(value)
185
- ''
209
+ ""
186
210
  elsif become_hash?(value)
187
- xml_value = Hash[value.map { |k,v| [k, deep_to_h(v)] }]
211
+ xml_value = value.transform_values { |v| deep_to_h(v) }
188
212
 
189
213
  # Turn { files: { file: #<StringIO> } } into { files: #<StringIO> } so it is compatible with
190
214
  # how multipart uploaded files from HTML appear
191
- xml_value['file'].is_a?(StringIO) ? xml_value['file'] : xml_value
215
+ xml_value["file"].is_a?(StringIO) ? xml_value["file"] : xml_value
192
216
  end
193
217
  end
194
218
 
195
219
  def become_content?(value)
196
- value['type'] == 'file' || (value['__content__'] && (value.keys.size == 1 || value['__content__'].present?))
220
+ value["type"] == "file" || (value["__content__"] && (value.keys.size == 1 || value["__content__"].present?))
197
221
  end
198
222
 
199
223
  def become_array?(value)
200
- value['type'] == 'array'
224
+ value["type"] == "array"
201
225
  end
202
226
 
203
227
  def become_empty_string?(value)
204
- # {"string" => true}
228
+ # { "string" => true }
205
229
  # No tests fail when the second term is removed.
206
- value['type'] == 'string' && value['nil'] != 'true'
230
+ value["type"] == "string" && value["nil"] != "true"
207
231
  end
208
232
 
209
233
  def become_hash?(value)
@@ -212,19 +236,19 @@ module ActiveSupport
212
236
 
213
237
  def nothing?(value)
214
238
  # blank or nil parsed values are represented by nil
215
- value.blank? || value['nil'] == 'true'
239
+ value.blank? || value["nil"] == "true"
216
240
  end
217
241
 
218
242
  def garbage?(value)
219
243
  # If the type is the only element which makes it then
220
244
  # this still makes the value nil, except if type is
221
- # a XML node(where type['value'] is a Hash)
222
- value['type'] && !value['type'].is_a?(::Hash) && value.size == 1
245
+ # an XML node(where type['value'] is a Hash)
246
+ value["type"] && !value["type"].is_a?(::Hash) && value.size == 1
223
247
  end
224
248
 
225
249
  def process_content(value)
226
- content = value['__content__']
227
- if parser = ActiveSupport::XmlMini::PARSING[value['type']]
250
+ content = value["__content__"]
251
+ if parser = ActiveSupport::XmlMini::PARSING[value["type"]]
228
252
  parser.arity == 1 ? parser.call(content) : parser.call(content, value)
229
253
  else
230
254
  content
@@ -235,7 +259,5 @@ module ActiveSupport
235
259
  value.map! { |i| deep_to_h(i) }
236
260
  value.length > 1 ? value : value.first
237
261
  end
238
-
239
262
  end
240
263
  end
241
-