activesupport 5.0.0 → 6.1.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 (268) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +343 -590
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +5 -4
  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 +11 -5
  8. data/lib/active_support/backtrace_cleaner.rb +33 -5
  9. data/lib/active_support/benchmarkable.rb +5 -3
  10. data/lib/active_support/builder.rb +3 -1
  11. data/lib/active_support/cache/file_store.rb +45 -53
  12. data/lib/active_support/cache/mem_cache_store.rb +81 -79
  13. data/lib/active_support/cache/memory_store.rb +69 -41
  14. data/lib/active_support/cache/null_store.rb +11 -4
  15. data/lib/active_support/cache/redis_cache_store.rb +493 -0
  16. data/lib/active_support/cache/strategy/local_cache.rb +74 -37
  17. data/lib/active_support/cache/strategy/local_cache_middleware.rb +10 -9
  18. data/lib/active_support/cache.rb +332 -161
  19. data/lib/active_support/callbacks.rb +657 -586
  20. data/lib/active_support/concern.rb +79 -6
  21. data/lib/active_support/concurrency/load_interlock_aware_monitor.rb +35 -0
  22. data/lib/active_support/concurrency/share_lock.rb +59 -19
  23. data/lib/active_support/configurable.rb +15 -17
  24. data/lib/active_support/configuration_file.rb +46 -0
  25. data/lib/active_support/core_ext/array/access.rb +21 -7
  26. data/lib/active_support/core_ext/array/conversions.rb +20 -18
  27. data/lib/active_support/core_ext/array/extract.rb +21 -0
  28. data/lib/active_support/core_ext/array/extract_options.rb +2 -0
  29. data/lib/active_support/core_ext/array/grouping.rb +3 -1
  30. data/lib/active_support/core_ext/array/inquiry.rb +3 -1
  31. data/lib/active_support/core_ext/array/wrap.rb +2 -0
  32. data/lib/active_support/core_ext/array.rb +9 -7
  33. data/lib/active_support/core_ext/benchmark.rb +5 -3
  34. data/lib/active_support/core_ext/big_decimal/conversions.rb +6 -6
  35. data/lib/active_support/core_ext/big_decimal.rb +3 -1
  36. data/lib/active_support/core_ext/class/attribute.rb +52 -49
  37. data/lib/active_support/core_ext/class/attribute_accessors.rb +3 -1
  38. data/lib/active_support/core_ext/class/subclasses.rb +18 -26
  39. data/lib/active_support/core_ext/class.rb +4 -2
  40. data/lib/active_support/core_ext/date/acts_like.rb +3 -1
  41. data/lib/active_support/core_ext/date/blank.rb +3 -1
  42. data/lib/active_support/core_ext/date/calculations.rb +16 -13
  43. data/lib/active_support/core_ext/date/conversions.rb +23 -21
  44. data/lib/active_support/core_ext/date/zones.rb +4 -2
  45. data/lib/active_support/core_ext/date.rb +7 -5
  46. data/lib/active_support/core_ext/date_and_time/calculations.rb +82 -53
  47. data/lib/active_support/core_ext/date_and_time/compatibility.rb +18 -5
  48. data/lib/active_support/core_ext/date_and_time/zones.rb +9 -9
  49. data/lib/active_support/core_ext/date_time/acts_like.rb +4 -2
  50. data/lib/active_support/core_ext/date_time/blank.rb +3 -1
  51. data/lib/active_support/core_ext/date_time/calculations.rb +23 -11
  52. data/lib/active_support/core_ext/date_time/compatibility.rb +15 -2
  53. data/lib/active_support/core_ext/date_time/conversions.rb +14 -13
  54. data/lib/active_support/core_ext/date_time.rb +7 -5
  55. data/lib/active_support/core_ext/digest/uuid.rb +7 -5
  56. data/lib/active_support/core_ext/digest.rb +3 -0
  57. data/lib/active_support/core_ext/enumerable.rb +165 -29
  58. data/lib/active_support/core_ext/file/atomic.rb +7 -5
  59. data/lib/active_support/core_ext/file.rb +3 -1
  60. data/lib/active_support/core_ext/hash/conversions.rb +40 -39
  61. data/lib/active_support/core_ext/hash/deep_merge.rb +8 -12
  62. data/lib/active_support/core_ext/hash/deep_transform_values.rb +46 -0
  63. data/lib/active_support/core_ext/hash/except.rb +4 -2
  64. data/lib/active_support/core_ext/hash/indifferent_access.rb +3 -2
  65. data/lib/active_support/core_ext/hash/keys.rb +9 -36
  66. data/lib/active_support/core_ext/hash/reverse_merge.rb +5 -2
  67. data/lib/active_support/core_ext/hash/slice.rb +8 -29
  68. data/lib/active_support/core_ext/hash.rb +10 -9
  69. data/lib/active_support/core_ext/integer/inflections.rb +3 -1
  70. data/lib/active_support/core_ext/integer/multiple.rb +3 -1
  71. data/lib/active_support/core_ext/integer/time.rb +11 -18
  72. data/lib/active_support/core_ext/integer.rb +5 -3
  73. data/lib/active_support/core_ext/kernel/concern.rb +3 -1
  74. data/lib/active_support/core_ext/kernel/reporting.rb +3 -1
  75. data/lib/active_support/core_ext/kernel/singleton_class.rb +2 -0
  76. data/lib/active_support/core_ext/kernel.rb +5 -4
  77. data/lib/active_support/core_ext/load_error.rb +2 -23
  78. data/lib/active_support/core_ext/marshal.rb +6 -2
  79. data/lib/active_support/core_ext/module/aliasing.rb +5 -48
  80. data/lib/active_support/core_ext/module/anonymous.rb +2 -0
  81. data/lib/active_support/core_ext/module/attr_internal.rb +7 -5
  82. data/lib/active_support/core_ext/module/attribute_accessors.rb +53 -59
  83. data/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb +31 -24
  84. data/lib/active_support/core_ext/module/concerning.rb +16 -11
  85. data/lib/active_support/core_ext/module/delegation.rb +159 -44
  86. data/lib/active_support/core_ext/module/deprecation.rb +2 -0
  87. data/lib/active_support/core_ext/module/introspection.rb +23 -26
  88. data/lib/active_support/core_ext/module/redefine_method.rb +40 -0
  89. data/lib/active_support/core_ext/module/remove_method.rb +5 -23
  90. data/lib/active_support/core_ext/module.rb +13 -12
  91. data/lib/active_support/core_ext/name_error.rb +36 -2
  92. data/lib/active_support/core_ext/numeric/bytes.rb +2 -0
  93. data/lib/active_support/core_ext/numeric/conversions.rb +129 -134
  94. data/lib/active_support/core_ext/numeric/time.rb +18 -26
  95. data/lib/active_support/core_ext/numeric.rb +5 -4
  96. data/lib/active_support/core_ext/object/acts_like.rb +12 -1
  97. data/lib/active_support/core_ext/object/blank.rb +14 -2
  98. data/lib/active_support/core_ext/object/conversions.rb +6 -4
  99. data/lib/active_support/core_ext/object/deep_dup.rb +4 -2
  100. data/lib/active_support/core_ext/object/duplicable.rb +13 -62
  101. data/lib/active_support/core_ext/object/inclusion.rb +3 -1
  102. data/lib/active_support/core_ext/object/instance_variables.rb +2 -0
  103. data/lib/active_support/core_ext/object/json.rb +42 -15
  104. data/lib/active_support/core_ext/object/to_param.rb +3 -1
  105. data/lib/active_support/core_ext/object/to_query.rb +10 -5
  106. data/lib/active_support/core_ext/object/try.rb +20 -8
  107. data/lib/active_support/core_ext/object/with_options.rb +15 -2
  108. data/lib/active_support/core_ext/object.rb +14 -12
  109. data/lib/active_support/core_ext/range/compare_range.rb +82 -0
  110. data/lib/active_support/core_ext/range/conversions.rb +35 -25
  111. data/lib/active_support/core_ext/range/each.rb +5 -2
  112. data/lib/active_support/core_ext/range/include_time_with_zone.rb +28 -0
  113. data/lib/active_support/core_ext/range/overlaps.rb +2 -0
  114. data/lib/active_support/core_ext/range.rb +7 -4
  115. data/lib/active_support/core_ext/regexp.rb +10 -1
  116. data/lib/active_support/core_ext/securerandom.rb +28 -6
  117. data/lib/active_support/core_ext/string/access.rb +9 -18
  118. data/lib/active_support/core_ext/string/behavior.rb +2 -0
  119. data/lib/active_support/core_ext/string/conversions.rb +5 -2
  120. data/lib/active_support/core_ext/string/exclude.rb +2 -0
  121. data/lib/active_support/core_ext/string/filters.rb +47 -4
  122. data/lib/active_support/core_ext/string/indent.rb +6 -4
  123. data/lib/active_support/core_ext/string/inflections.rb +78 -29
  124. data/lib/active_support/core_ext/string/inquiry.rb +4 -1
  125. data/lib/active_support/core_ext/string/multibyte.rb +10 -5
  126. data/lib/active_support/core_ext/string/output_safety.rb +86 -31
  127. data/lib/active_support/core_ext/string/starts_ends_with.rb +4 -2
  128. data/lib/active_support/core_ext/string/strip.rb +5 -1
  129. data/lib/active_support/core_ext/string/zones.rb +4 -2
  130. data/lib/active_support/core_ext/string.rb +15 -13
  131. data/lib/active_support/core_ext/symbol/starts_ends_with.rb +14 -0
  132. data/lib/active_support/core_ext/symbol.rb +3 -0
  133. data/lib/active_support/core_ext/time/acts_like.rb +3 -1
  134. data/lib/active_support/core_ext/time/calculations.rb +117 -45
  135. data/lib/active_support/core_ext/time/compatibility.rb +13 -2
  136. data/lib/active_support/core_ext/time/conversions.rb +18 -12
  137. data/lib/active_support/core_ext/time/zones.rb +9 -7
  138. data/lib/active_support/core_ext/time.rb +7 -5
  139. data/lib/active_support/core_ext/uri.rb +12 -7
  140. data/lib/active_support/core_ext.rb +3 -2
  141. data/lib/active_support/current_attributes/test_helper.rb +13 -0
  142. data/lib/active_support/current_attributes.rb +208 -0
  143. data/lib/active_support/dependencies/autoload.rb +2 -0
  144. data/lib/active_support/dependencies/interlock.rb +7 -1
  145. data/lib/active_support/dependencies/zeitwerk_integration.rb +117 -0
  146. data/lib/active_support/dependencies.rb +172 -98
  147. data/lib/active_support/deprecation/behaviors.rb +45 -13
  148. data/lib/active_support/deprecation/constant_accessor.rb +52 -0
  149. data/lib/active_support/deprecation/disallowed.rb +56 -0
  150. data/lib/active_support/deprecation/instance_delegator.rb +16 -2
  151. data/lib/active_support/deprecation/method_wrappers.rb +32 -17
  152. data/lib/active_support/deprecation/proxy_wrappers.rb +35 -7
  153. data/lib/active_support/deprecation/reporting.rb +61 -16
  154. data/lib/active_support/deprecation.rb +17 -9
  155. data/lib/active_support/descendants_tracker.rb +61 -9
  156. data/lib/active_support/digest.rb +20 -0
  157. data/lib/active_support/duration/iso8601_parser.rb +67 -66
  158. data/lib/active_support/duration/iso8601_serializer.rb +25 -17
  159. data/lib/active_support/duration.rb +349 -46
  160. data/lib/active_support/encrypted_configuration.rb +45 -0
  161. data/lib/active_support/encrypted_file.rb +117 -0
  162. data/lib/active_support/environment_inquirer.rb +20 -0
  163. data/lib/active_support/evented_file_update_checker.rb +88 -112
  164. data/lib/active_support/execution_wrapper.rb +25 -13
  165. data/lib/active_support/executor.rb +3 -1
  166. data/lib/active_support/file_update_checker.rb +56 -51
  167. data/lib/active_support/fork_tracker.rb +62 -0
  168. data/lib/active_support/gem_version.rb +4 -2
  169. data/lib/active_support/gzip.rb +7 -5
  170. data/lib/active_support/hash_with_indifferent_access.rb +153 -49
  171. data/lib/active_support/i18n.rb +9 -6
  172. data/lib/active_support/i18n_railtie.rb +30 -20
  173. data/lib/active_support/inflections.rb +13 -11
  174. data/lib/active_support/inflector/inflections.rb +28 -15
  175. data/lib/active_support/inflector/methods.rb +120 -109
  176. data/lib/active_support/inflector/transliterate.rb +60 -25
  177. data/lib/active_support/inflector.rb +7 -5
  178. data/lib/active_support/json/decoding.rb +30 -29
  179. data/lib/active_support/json/encoding.rb +22 -11
  180. data/lib/active_support/json.rb +4 -2
  181. data/lib/active_support/key_generator.rb +6 -36
  182. data/lib/active_support/lazy_load_hooks.rb +53 -20
  183. data/lib/active_support/locale/en.rb +33 -0
  184. data/lib/active_support/locale/en.yml +7 -3
  185. data/lib/active_support/log_subscriber/test_helper.rb +11 -9
  186. data/lib/active_support/log_subscriber.rb +51 -18
  187. data/lib/active_support/logger.rb +9 -22
  188. data/lib/active_support/logger_silence.rb +14 -21
  189. data/lib/active_support/logger_thread_safe_level.rb +55 -8
  190. data/lib/active_support/message_encryptor.rb +170 -53
  191. data/lib/active_support/message_verifier.rb +91 -20
  192. data/lib/active_support/messages/metadata.rb +80 -0
  193. data/lib/active_support/messages/rotation_configuration.rb +23 -0
  194. data/lib/active_support/messages/rotator.rb +57 -0
  195. data/lib/active_support/multibyte/chars.rb +24 -78
  196. data/lib/active_support/multibyte/unicode.rb +21 -352
  197. data/lib/active_support/multibyte.rb +4 -2
  198. data/lib/active_support/notifications/fanout.rb +121 -19
  199. data/lib/active_support/notifications/instrumenter.rb +78 -14
  200. data/lib/active_support/notifications.rb +80 -12
  201. data/lib/active_support/number_helper/number_converter.rb +17 -16
  202. data/lib/active_support/number_helper/number_to_currency_converter.rb +6 -9
  203. data/lib/active_support/number_helper/number_to_delimited_converter.rb +5 -3
  204. data/lib/active_support/number_helper/number_to_human_converter.rb +13 -12
  205. data/lib/active_support/number_helper/number_to_human_size_converter.rb +11 -13
  206. data/lib/active_support/number_helper/number_to_percentage_converter.rb +5 -1
  207. data/lib/active_support/number_helper/number_to_phone_converter.rb +5 -4
  208. data/lib/active_support/number_helper/number_to_rounded_converter.rb +18 -55
  209. data/lib/active_support/number_helper/rounding_helper.rb +50 -0
  210. data/lib/active_support/number_helper.rb +45 -16
  211. data/lib/active_support/option_merger.rb +25 -4
  212. data/lib/active_support/ordered_hash.rb +6 -4
  213. data/lib/active_support/ordered_options.rb +23 -9
  214. data/lib/active_support/parameter_filter.rb +133 -0
  215. data/lib/active_support/per_thread_registry.rb +7 -5
  216. data/lib/active_support/proxy_object.rb +2 -0
  217. data/lib/active_support/rails.rb +8 -9
  218. data/lib/active_support/railtie.rb +62 -11
  219. data/lib/active_support/reloader.rb +12 -11
  220. data/lib/active_support/rescuable.rb +20 -11
  221. data/lib/active_support/secure_compare_rotator.rb +51 -0
  222. data/lib/active_support/security_utils.rb +26 -15
  223. data/lib/active_support/string_inquirer.rb +12 -3
  224. data/lib/active_support/subscriber.rb +77 -23
  225. data/lib/active_support/tagged_logging.rb +52 -17
  226. data/lib/active_support/test_case.rb +106 -29
  227. data/lib/active_support/testing/assertions.rb +144 -8
  228. data/lib/active_support/testing/autorun.rb +5 -10
  229. data/lib/active_support/testing/constant_lookup.rb +2 -1
  230. data/lib/active_support/testing/declarative.rb +3 -1
  231. data/lib/active_support/testing/deprecation.rb +4 -2
  232. data/lib/active_support/testing/file_fixtures.rb +4 -0
  233. data/lib/active_support/testing/isolation.rb +19 -24
  234. data/lib/active_support/testing/method_call_assertions.rb +31 -2
  235. data/lib/active_support/testing/parallelization/server.rb +78 -0
  236. data/lib/active_support/testing/parallelization/worker.rb +100 -0
  237. data/lib/active_support/testing/parallelization.rb +51 -0
  238. data/lib/active_support/testing/setup_and_teardown.rb +13 -8
  239. data/lib/active_support/testing/stream.rb +30 -29
  240. data/lib/active_support/testing/tagged_logging.rb +3 -1
  241. data/lib/active_support/testing/time_helpers.rb +125 -24
  242. data/lib/active_support/time.rb +14 -12
  243. data/lib/active_support/time_with_zone.rb +142 -55
  244. data/lib/active_support/values/time_zone.rb +160 -53
  245. data/lib/active_support/version.rb +3 -1
  246. data/lib/active_support/xml_mini/jdom.rb +115 -114
  247. data/lib/active_support/xml_mini/libxml.rb +15 -14
  248. data/lib/active_support/xml_mini/libxmlsax.rb +16 -18
  249. data/lib/active_support/xml_mini/nokogiri.rb +13 -13
  250. data/lib/active_support/xml_mini/nokogirisax.rb +15 -16
  251. data/lib/active_support/xml_mini/rexml.rb +18 -9
  252. data/lib/active_support/xml_mini.rb +44 -42
  253. data/lib/active_support.rb +19 -10
  254. metadata +79 -37
  255. data/lib/active_support/concurrency/latch.rb +0 -19
  256. data/lib/active_support/core_ext/array/prepend_and_append.rb +0 -7
  257. data/lib/active_support/core_ext/hash/compact.rb +0 -20
  258. data/lib/active_support/core_ext/hash/transform_values.rb +0 -29
  259. data/lib/active_support/core_ext/kernel/agnostics.rb +0 -11
  260. data/lib/active_support/core_ext/kernel/debugger.rb +0 -3
  261. data/lib/active_support/core_ext/module/method_transplanting.rb +0 -3
  262. data/lib/active_support/core_ext/module/qualified_const.rb +0 -70
  263. data/lib/active_support/core_ext/module/reachable.rb +0 -8
  264. data/lib/active_support/core_ext/numeric/inquiry.rb +0 -26
  265. data/lib/active_support/core_ext/range/include_range.rb +0 -23
  266. data/lib/active_support/core_ext/struct.rb +0 -3
  267. data/lib/active_support/core_ext/time/marshal.rb +0 -3
  268. data/lib/active_support/values/unicode_tables.dat +0 -0
@@ -1,5 +1,9 @@
1
- require 'active_support/core_ext/array/conversions'
2
- require 'active_support/core_ext/object/acts_like'
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/core_ext/array/conversions"
4
+ require "active_support/core_ext/module/delegation"
5
+ require "active_support/core_ext/object/acts_like"
6
+ require "active_support/core_ext/string/filters"
3
7
 
4
8
  module ActiveSupport
5
9
  # Provides accurate date and time measurements using Date#advance and
@@ -7,22 +11,241 @@ module ActiveSupport
7
11
  #
8
12
  # 1.month.ago # equivalent to Time.now.advance(months: -1)
9
13
  class Duration
14
+ class Scalar < Numeric #:nodoc:
15
+ attr_reader :value
16
+ delegate :to_i, :to_f, :to_s, to: :value
17
+
18
+ def initialize(value)
19
+ @value = value
20
+ end
21
+
22
+ def coerce(other)
23
+ [Scalar.new(other), self]
24
+ end
25
+
26
+ def -@
27
+ Scalar.new(-value)
28
+ end
29
+
30
+ def <=>(other)
31
+ if Scalar === other || Duration === other
32
+ value <=> other.value
33
+ elsif Numeric === other
34
+ value <=> other
35
+ else
36
+ nil
37
+ end
38
+ end
39
+
40
+ def +(other)
41
+ if Duration === other
42
+ seconds = value + other.parts.fetch(:seconds, 0)
43
+ new_parts = other.parts.merge(seconds: seconds)
44
+ new_value = value + other.value
45
+
46
+ Duration.new(new_value, new_parts)
47
+ else
48
+ calculate(:+, other)
49
+ end
50
+ end
51
+
52
+ def -(other)
53
+ if Duration === other
54
+ seconds = value - other.parts.fetch(:seconds, 0)
55
+ new_parts = other.parts.transform_values(&:-@)
56
+ new_parts = new_parts.merge(seconds: seconds)
57
+ new_value = value - other.value
58
+
59
+ Duration.new(new_value, new_parts)
60
+ else
61
+ calculate(:-, other)
62
+ end
63
+ end
64
+
65
+ def *(other)
66
+ if Duration === other
67
+ new_parts = other.parts.transform_values { |other_value| value * other_value }
68
+ new_value = value * other.value
69
+
70
+ Duration.new(new_value, new_parts)
71
+ else
72
+ calculate(:*, other)
73
+ end
74
+ end
75
+
76
+ def /(other)
77
+ if Duration === other
78
+ value / other.value
79
+ else
80
+ calculate(:/, other)
81
+ end
82
+ end
83
+
84
+ def %(other)
85
+ if Duration === other
86
+ Duration.build(value % other.value)
87
+ else
88
+ calculate(:%, other)
89
+ end
90
+ end
91
+
92
+ private
93
+ def calculate(op, other)
94
+ if Scalar === other
95
+ Scalar.new(value.public_send(op, other.value))
96
+ elsif Numeric === other
97
+ Scalar.new(value.public_send(op, other))
98
+ else
99
+ raise_type_error(other)
100
+ end
101
+ end
102
+
103
+ def raise_type_error(other)
104
+ raise TypeError, "no implicit conversion of #{other.class} into #{self.class}"
105
+ end
106
+ end
107
+
108
+ SECONDS_PER_MINUTE = 60
109
+ SECONDS_PER_HOUR = 3600
110
+ SECONDS_PER_DAY = 86400
111
+ SECONDS_PER_WEEK = 604800
112
+ SECONDS_PER_MONTH = 2629746 # 1/12 of a gregorian year
113
+ SECONDS_PER_YEAR = 31556952 # length of a gregorian year (365.2425 days)
114
+
115
+ PARTS_IN_SECONDS = {
116
+ seconds: 1,
117
+ minutes: SECONDS_PER_MINUTE,
118
+ hours: SECONDS_PER_HOUR,
119
+ days: SECONDS_PER_DAY,
120
+ weeks: SECONDS_PER_WEEK,
121
+ months: SECONDS_PER_MONTH,
122
+ years: SECONDS_PER_YEAR
123
+ }.freeze
124
+
125
+ PARTS = [:years, :months, :weeks, :days, :hours, :minutes, :seconds].freeze
126
+
10
127
  attr_accessor :value, :parts
11
128
 
12
- autoload :ISO8601Parser, 'active_support/duration/iso8601_parser'
13
- autoload :ISO8601Serializer, 'active_support/duration/iso8601_serializer'
129
+ autoload :ISO8601Parser, "active_support/duration/iso8601_parser"
130
+ autoload :ISO8601Serializer, "active_support/duration/iso8601_serializer"
131
+
132
+ class << self
133
+ # Creates a new Duration from string formatted according to ISO 8601 Duration.
134
+ #
135
+ # See {ISO 8601}[https://en.wikipedia.org/wiki/ISO_8601#Durations] for more information.
136
+ # This method allows negative parts to be present in pattern.
137
+ # If invalid string is provided, it will raise +ActiveSupport::Duration::ISO8601Parser::ParsingError+.
138
+ def parse(iso8601duration)
139
+ parts = ISO8601Parser.new(iso8601duration).parse!
140
+ new(calculate_total_seconds(parts), parts)
141
+ end
142
+
143
+ def ===(other) #:nodoc:
144
+ other.is_a?(Duration)
145
+ rescue ::NoMethodError
146
+ false
147
+ end
148
+
149
+ def seconds(value) #:nodoc:
150
+ new(value, seconds: value)
151
+ end
152
+
153
+ def minutes(value) #:nodoc:
154
+ new(value * SECONDS_PER_MINUTE, minutes: value)
155
+ end
156
+
157
+ def hours(value) #:nodoc:
158
+ new(value * SECONDS_PER_HOUR, hours: value)
159
+ end
160
+
161
+ def days(value) #:nodoc:
162
+ new(value * SECONDS_PER_DAY, days: value)
163
+ end
164
+
165
+ def weeks(value) #:nodoc:
166
+ new(value * SECONDS_PER_WEEK, weeks: value)
167
+ end
168
+
169
+ def months(value) #:nodoc:
170
+ new(value * SECONDS_PER_MONTH, months: value)
171
+ end
172
+
173
+ def years(value) #:nodoc:
174
+ new(value * SECONDS_PER_YEAR, years: value)
175
+ end
176
+
177
+ # Creates a new Duration from a seconds value that is converted
178
+ # to the individual parts:
179
+ #
180
+ # ActiveSupport::Duration.build(31556952).parts # => {:years=>1}
181
+ # ActiveSupport::Duration.build(2716146).parts # => {:months=>1, :days=>1}
182
+ #
183
+ def build(value)
184
+ unless value.is_a?(::Numeric)
185
+ raise TypeError, "can't build an #{self.name} from a #{value.class.name}"
186
+ end
187
+
188
+ parts = {}
189
+ remainder = value.round(9)
190
+
191
+ PARTS.each do |part|
192
+ unless part == :seconds
193
+ part_in_seconds = PARTS_IN_SECONDS[part]
194
+ parts[part] = remainder.div(part_in_seconds)
195
+ remainder %= part_in_seconds
196
+ end
197
+ end unless value == 0
198
+
199
+ parts[:seconds] = remainder
200
+
201
+ new(value, parts)
202
+ end
203
+
204
+ private
205
+ def calculate_total_seconds(parts)
206
+ parts.inject(0) do |total, (part, value)|
207
+ total + value * PARTS_IN_SECONDS[part]
208
+ end
209
+ end
210
+ end
14
211
 
15
212
  def initialize(value, parts) #:nodoc:
16
213
  @value, @parts = value, parts
214
+ @parts.reject! { |k, v| v.zero? } unless value == 0
215
+ end
216
+
217
+ def coerce(other) #:nodoc:
218
+ case other
219
+ when Scalar
220
+ [other, self]
221
+ when Duration
222
+ [Scalar.new(other.value), self]
223
+ else
224
+ [Scalar.new(other), self]
225
+ end
226
+ end
227
+
228
+ # Compares one Duration with another or a Numeric to this Duration.
229
+ # Numeric values are treated as seconds.
230
+ def <=>(other)
231
+ if Duration === other
232
+ value <=> other.value
233
+ elsif Numeric === other
234
+ value <=> other
235
+ end
17
236
  end
18
237
 
19
238
  # Adds another Duration or a Numeric to this Duration. Numeric values
20
239
  # are treated as seconds.
21
240
  def +(other)
22
241
  if Duration === other
23
- Duration.new(value + other.value, @parts + other.parts)
242
+ parts = @parts.merge(other.parts) do |_key, value, other_value|
243
+ value + other_value
244
+ end
245
+ Duration.new(value + other.value, parts)
24
246
  else
25
- Duration.new(value + other, @parts + [[:seconds, other]])
247
+ seconds = @parts.fetch(:seconds, 0) + other
248
+ Duration.new(value + other, @parts.merge(seconds: seconds))
26
249
  end
27
250
  end
28
251
 
@@ -32,8 +255,48 @@ module ActiveSupport
32
255
  self + (-other)
33
256
  end
34
257
 
258
+ # Multiplies this Duration by a Numeric and returns a new Duration.
259
+ def *(other)
260
+ if Scalar === other || Duration === other
261
+ Duration.new(value * other.value, parts.transform_values { |number| number * other.value })
262
+ elsif Numeric === other
263
+ Duration.new(value * other, parts.transform_values { |number| number * other })
264
+ else
265
+ raise_type_error(other)
266
+ end
267
+ end
268
+
269
+ # Divides this Duration by a Numeric and returns a new Duration.
270
+ def /(other)
271
+ if Scalar === other
272
+ Duration.new(value / other.value, parts.transform_values { |number| number / other.value })
273
+ elsif Duration === other
274
+ value / other.value
275
+ elsif Numeric === other
276
+ Duration.new(value / other, parts.transform_values { |number| number / other })
277
+ else
278
+ raise_type_error(other)
279
+ end
280
+ end
281
+
282
+ # Returns the modulo of this Duration by another Duration or Numeric.
283
+ # Numeric values are treated as seconds.
284
+ def %(other)
285
+ if Duration === other || Scalar === other
286
+ Duration.build(value % other.value)
287
+ elsif Numeric === other
288
+ Duration.build(value % other)
289
+ else
290
+ raise_type_error(other)
291
+ end
292
+ end
293
+
35
294
  def -@ #:nodoc:
36
- Duration.new(-value, parts.map { |type,number| [type, -number] })
295
+ Duration.new(-value, parts.transform_values(&:-@))
296
+ end
297
+
298
+ def +@ #:nodoc:
299
+ self
37
300
  end
38
301
 
39
302
  def is_a?(klass) #:nodoc:
@@ -70,22 +333,65 @@ module ActiveSupport
70
333
  # 1.day.to_i # => 86400
71
334
  #
72
335
  # Note that this conversion makes some assumptions about the
73
- # duration of some periods, e.g. months are always 30 days
74
- # and years are 365.25 days:
336
+ # duration of some periods, e.g. months are always 1/12 of year
337
+ # and years are 365.2425 days:
75
338
  #
76
- # # equivalent to 30.days.to_i
77
- # 1.month.to_i # => 2592000
339
+ # # equivalent to (1.year / 12).to_i
340
+ # 1.month.to_i # => 2629746
78
341
  #
79
- # # equivalent to 365.25.days.to_i
80
- # 1.year.to_i # => 31557600
342
+ # # equivalent to 365.2425.days.to_i
343
+ # 1.year.to_i # => 31556952
81
344
  #
82
345
  # In such cases, Ruby's core
83
- # Date[http://ruby-doc.org/stdlib/libdoc/date/rdoc/Date.html] and
84
- # Time[http://ruby-doc.org/stdlib/libdoc/time/rdoc/Time.html] should be used for precision
346
+ # Date[https://ruby-doc.org/stdlib/libdoc/date/rdoc/Date.html] and
347
+ # Time[https://ruby-doc.org/stdlib/libdoc/time/rdoc/Time.html] should be used for precision
85
348
  # date and time arithmetic.
86
349
  def to_i
87
350
  @value.to_i
88
351
  end
352
+ alias :in_seconds :to_i
353
+
354
+ # Returns the amount of minutes a duration covers as a float
355
+ #
356
+ # 1.day.in_minutes # => 1440.0
357
+ def in_minutes
358
+ in_seconds / SECONDS_PER_MINUTE.to_f
359
+ end
360
+
361
+ # Returns the amount of hours a duration covers as a float
362
+ #
363
+ # 1.day.in_hours # => 24.0
364
+ def in_hours
365
+ in_seconds / SECONDS_PER_HOUR.to_f
366
+ end
367
+
368
+ # Returns the amount of days a duration covers as a float
369
+ #
370
+ # 12.hours.in_days # => 0.5
371
+ def in_days
372
+ in_seconds / SECONDS_PER_DAY.to_f
373
+ end
374
+
375
+ # Returns the amount of weeks a duration covers as a float
376
+ #
377
+ # 2.months.in_weeks # => 8.696
378
+ def in_weeks
379
+ in_seconds / SECONDS_PER_WEEK.to_f
380
+ end
381
+
382
+ # Returns the amount of months a duration covers as a float
383
+ #
384
+ # 9.weeks.in_months # => 2.07
385
+ def in_months
386
+ in_seconds / SECONDS_PER_MONTH.to_f
387
+ end
388
+
389
+ # Returns the amount of years a duration covers as a float
390
+ #
391
+ # 30.days.in_years # => 0.082
392
+ def in_years
393
+ in_seconds / SECONDS_PER_YEAR.to_f
394
+ end
89
395
 
90
396
  # Returns +true+ if +other+ is also a Duration instance, which has the
91
397
  # same parts as this one.
@@ -97,18 +403,13 @@ module ActiveSupport
97
403
  @value.hash
98
404
  end
99
405
 
100
- def self.===(other) #:nodoc:
101
- other.is_a?(Duration)
102
- rescue ::NoMethodError
103
- false
104
- end
105
-
106
406
  # Calculates a new Time or Date that is as far in the future
107
407
  # as this Duration represents.
108
408
  def since(time = ::Time.current)
109
409
  sum(1, time)
110
410
  end
111
411
  alias :from_now :since
412
+ alias :after :since
112
413
 
113
414
  # Calculates a new Time or Date that is as far in the past
114
415
  # as this Duration represents.
@@ -116,12 +417,14 @@ module ActiveSupport
116
417
  sum(-1, time)
117
418
  end
118
419
  alias :until :ago
420
+ alias :before :ago
119
421
 
120
422
  def inspect #:nodoc:
423
+ return "#{value} seconds" if parts.empty?
424
+
121
425
  parts.
122
- reduce(::Hash.new(0)) { |h,(l,r)| h[l] += r; h }.
123
- sort_by {|unit, _ | [:years, :months, :weeks, :days, :hours, :minutes, :seconds].index(unit)}.
124
- map {|unit, val| "#{val} #{val == 1 ? unit.to_s.chop : unit.to_s}"}.
426
+ sort_by { |unit, _ | PARTS.index(unit) }.
427
+ map { |unit, val| "#{val} #{val == 1 ? unit.to_s.chop : unit.to_s}" }.
125
428
  to_sentence(locale: ::I18n.default_locale)
126
429
  end
127
430
 
@@ -129,19 +432,12 @@ module ActiveSupport
129
432
  to_i
130
433
  end
131
434
 
132
- def respond_to_missing?(method, include_private=false) #:nodoc:
133
- @value.respond_to?(method, include_private)
435
+ def init_with(coder) #:nodoc:
436
+ initialize(coder["value"], coder["parts"])
134
437
  end
135
438
 
136
- # Creates a new Duration from string formatted according to ISO 8601 Duration.
137
- #
138
- # See {ISO 8601}[http://en.wikipedia.org/wiki/ISO_8601#Durations] for more information.
139
- # This method allows negative parts to be present in pattern.
140
- # If invalid string is provided, it will raise +ActiveSupport::Duration::ISO8601Parser::ParsingError+.
141
- def self.parse(iso8601duration)
142
- parts = ISO8601Parser.new(iso8601duration).parse!
143
- time = ::Time.current
144
- new(time.advance(parts) - time, parts)
439
+ def encode_with(coder) #:nodoc:
440
+ coder.map = { "value" => @value, "parts" => @parts }
145
441
  end
146
442
 
147
443
  # Build ISO 8601 Duration string for this duration.
@@ -150,13 +446,16 @@ module ActiveSupport
150
446
  ISO8601Serializer.new(self, precision: precision).serialize
151
447
  end
152
448
 
153
- delegate :<=>, to: :value
154
-
155
- protected
449
+ private
450
+ def sum(sign, time = ::Time.current)
451
+ unless time.acts_like?(:time) || time.acts_like?(:date)
452
+ raise ::ArgumentError, "expected a time or date, got #{time.inspect}"
453
+ end
156
454
 
157
- def sum(sign, time = ::Time.current) #:nodoc:
158
- parts.inject(time) do |t,(type,number)|
159
- if t.acts_like?(:time) || t.acts_like?(:date)
455
+ if parts.empty?
456
+ time.since(sign * value)
457
+ else
458
+ parts.inject(time) do |t, (type, number)|
160
459
  if type == :seconds
161
460
  t.since(sign * number)
162
461
  elsif type == :minutes
@@ -166,16 +465,20 @@ module ActiveSupport
166
465
  else
167
466
  t.advance(type => sign * number)
168
467
  end
169
- else
170
- raise ::ArgumentError, "expected a time or date, got #{time.inspect}"
171
468
  end
172
469
  end
173
470
  end
174
471
 
175
- private
472
+ def respond_to_missing?(method, _)
473
+ value.respond_to?(method)
474
+ end
475
+
476
+ def method_missing(method, *args, &block)
477
+ value.public_send(method, *args, &block)
478
+ end
176
479
 
177
- def method_missing(method, *args, &block) #:nodoc:
178
- value.send(method, *args, &block)
480
+ def raise_type_error(other)
481
+ raise TypeError, "no implicit conversion of #{other.class} into #{self.class}"
179
482
  end
180
483
  end
181
484
  end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "yaml"
4
+ require "active_support/encrypted_file"
5
+ require "active_support/ordered_options"
6
+ require "active_support/core_ext/object/inclusion"
7
+ require "active_support/core_ext/module/delegation"
8
+
9
+ module ActiveSupport
10
+ class EncryptedConfiguration < EncryptedFile
11
+ delegate :[], :fetch, to: :config
12
+ delegate_missing_to :options
13
+
14
+ def initialize(config_path:, key_path:, env_key:, raise_if_missing_key:)
15
+ super content_path: config_path, key_path: key_path,
16
+ env_key: env_key, raise_if_missing_key: raise_if_missing_key
17
+ end
18
+
19
+ # Allow a config to be started without a file present
20
+ def read
21
+ super
22
+ rescue ActiveSupport::EncryptedFile::MissingContentError
23
+ ""
24
+ end
25
+
26
+ def write(contents)
27
+ deserialize(contents)
28
+
29
+ super
30
+ end
31
+
32
+ def config
33
+ @config ||= deserialize(read).deep_symbolize_keys
34
+ end
35
+
36
+ private
37
+ def options
38
+ @options ||= ActiveSupport::InheritableOptions.new(config)
39
+ end
40
+
41
+ def deserialize(config)
42
+ YAML.load(config).presence || {}
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,117 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "pathname"
4
+ require "tmpdir"
5
+ require "active_support/message_encryptor"
6
+
7
+ module ActiveSupport
8
+ class EncryptedFile
9
+ class MissingContentError < RuntimeError
10
+ def initialize(content_path)
11
+ super "Missing encrypted content file in #{content_path}."
12
+ end
13
+ end
14
+
15
+ class MissingKeyError < RuntimeError
16
+ def initialize(key_path:, env_key:)
17
+ super \
18
+ "Missing encryption key to decrypt file with. " +
19
+ "Ask your team for your master key and write it to #{key_path} or put it in the ENV['#{env_key}']."
20
+ end
21
+ end
22
+
23
+ class InvalidKeyLengthError < RuntimeError
24
+ def initialize
25
+ super "Encryption key must be exactly #{EncryptedFile.expected_key_length} characters."
26
+ end
27
+ end
28
+
29
+ CIPHER = "aes-128-gcm"
30
+
31
+ def self.generate_key
32
+ SecureRandom.hex(ActiveSupport::MessageEncryptor.key_len(CIPHER))
33
+ end
34
+
35
+ def self.expected_key_length # :nodoc:
36
+ @expected_key_length ||= generate_key.length
37
+ end
38
+
39
+
40
+ attr_reader :content_path, :key_path, :env_key, :raise_if_missing_key
41
+
42
+ def initialize(content_path:, key_path:, env_key:, raise_if_missing_key:)
43
+ @content_path = Pathname.new(content_path).yield_self { |path| path.symlink? ? path.realpath : path }
44
+ @key_path = Pathname.new(key_path)
45
+ @env_key, @raise_if_missing_key = env_key, raise_if_missing_key
46
+ end
47
+
48
+ def key
49
+ read_env_key || read_key_file || handle_missing_key
50
+ end
51
+
52
+ def read
53
+ if !key.nil? && content_path.exist?
54
+ decrypt content_path.binread
55
+ else
56
+ raise MissingContentError, content_path
57
+ end
58
+ end
59
+
60
+ def write(contents)
61
+ IO.binwrite "#{content_path}.tmp", encrypt(contents)
62
+ FileUtils.mv "#{content_path}.tmp", content_path
63
+ end
64
+
65
+ def change(&block)
66
+ writing read, &block
67
+ end
68
+
69
+
70
+ private
71
+ def writing(contents)
72
+ tmp_file = "#{Process.pid}.#{content_path.basename.to_s.chomp('.enc')}"
73
+ tmp_path = Pathname.new File.join(Dir.tmpdir, tmp_file)
74
+ tmp_path.binwrite contents
75
+
76
+ yield tmp_path
77
+
78
+ updated_contents = tmp_path.binread
79
+
80
+ write(updated_contents) if updated_contents != contents
81
+ ensure
82
+ FileUtils.rm(tmp_path) if tmp_path&.exist?
83
+ end
84
+
85
+
86
+ def encrypt(contents)
87
+ check_key_length
88
+ encryptor.encrypt_and_sign contents
89
+ end
90
+
91
+ def decrypt(contents)
92
+ encryptor.decrypt_and_verify contents
93
+ end
94
+
95
+ def encryptor
96
+ @encryptor ||= ActiveSupport::MessageEncryptor.new([ key ].pack("H*"), cipher: CIPHER)
97
+ end
98
+
99
+
100
+ def read_env_key
101
+ ENV[env_key]
102
+ end
103
+
104
+ def read_key_file
105
+ return @key_file_contents if defined?(@key_file_contents)
106
+ @key_file_contents = (key_path.binread.strip if key_path.exist?)
107
+ end
108
+
109
+ def handle_missing_key
110
+ raise MissingKeyError.new(key_path: key_path, env_key: env_key) if raise_if_missing_key
111
+ end
112
+
113
+ def check_key_length
114
+ raise InvalidKeyLengthError if key&.length != self.class.expected_key_length
115
+ end
116
+ end
117
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/string_inquirer"
4
+
5
+ module ActiveSupport
6
+ class EnvironmentInquirer < StringInquirer #:nodoc:
7
+ DEFAULT_ENVIRONMENTS = ["development", "test", "production"]
8
+ def initialize(env)
9
+ super(env)
10
+
11
+ DEFAULT_ENVIRONMENTS.each do |default|
12
+ instance_variable_set :"@#{default}", env == default
13
+ end
14
+ end
15
+
16
+ DEFAULT_ENVIRONMENTS.each do |env|
17
+ class_eval "def #{env}?; @#{env}; end"
18
+ end
19
+ end
20
+ end