activesupport 5.2.4.4 → 6.1.1

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 (187) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +353 -435
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +4 -3
  5. data/lib/active_support.rb +14 -1
  6. data/lib/active_support/actionable_error.rb +48 -0
  7. data/lib/active_support/array_inquirer.rb +4 -2
  8. data/lib/active_support/backtrace_cleaner.rb +29 -3
  9. data/lib/active_support/benchmarkable.rb +1 -1
  10. data/lib/active_support/cache.rb +142 -78
  11. data/lib/active_support/cache/file_store.rb +33 -33
  12. data/lib/active_support/cache/mem_cache_store.rb +32 -20
  13. data/lib/active_support/cache/memory_store.rb +59 -33
  14. data/lib/active_support/cache/null_store.rb +8 -3
  15. data/lib/active_support/cache/redis_cache_store.rb +70 -43
  16. data/lib/active_support/cache/strategy/local_cache.rb +41 -26
  17. data/lib/active_support/callbacks.rb +81 -64
  18. data/lib/active_support/concern.rb +70 -3
  19. data/lib/active_support/concurrency/load_interlock_aware_monitor.rb +18 -0
  20. data/lib/active_support/concurrency/share_lock.rb +0 -1
  21. data/lib/active_support/configurable.rb +10 -14
  22. data/lib/active_support/configuration_file.rb +46 -0
  23. data/lib/active_support/core_ext.rb +1 -1
  24. data/lib/active_support/core_ext/array.rb +1 -1
  25. data/lib/active_support/core_ext/array/access.rb +18 -6
  26. data/lib/active_support/core_ext/array/conversions.rb +5 -5
  27. data/lib/active_support/core_ext/array/extract.rb +21 -0
  28. data/lib/active_support/core_ext/benchmark.rb +2 -2
  29. data/lib/active_support/core_ext/class/attribute.rb +32 -47
  30. data/lib/active_support/core_ext/class/subclasses.rb +17 -38
  31. data/lib/active_support/core_ext/date/calculations.rb +6 -5
  32. data/lib/active_support/core_ext/date/conversions.rb +2 -1
  33. data/lib/active_support/core_ext/date_and_time/calculations.rb +37 -47
  34. data/lib/active_support/core_ext/date_and_time/compatibility.rb +15 -0
  35. data/lib/active_support/core_ext/date_and_time/zones.rb +0 -1
  36. data/lib/active_support/core_ext/date_time/calculations.rb +1 -1
  37. data/lib/active_support/core_ext/date_time/conversions.rb +0 -1
  38. data/lib/active_support/core_ext/enumerable.rb +171 -75
  39. data/lib/active_support/core_ext/hash.rb +1 -2
  40. data/lib/active_support/core_ext/hash/conversions.rb +3 -3
  41. data/lib/active_support/core_ext/hash/deep_transform_values.rb +46 -0
  42. data/lib/active_support/core_ext/hash/except.rb +2 -2
  43. data/lib/active_support/core_ext/hash/keys.rb +1 -30
  44. data/lib/active_support/core_ext/hash/slice.rb +6 -27
  45. data/lib/active_support/core_ext/integer/multiple.rb +1 -1
  46. data/lib/active_support/core_ext/kernel.rb +0 -1
  47. data/lib/active_support/core_ext/load_error.rb +1 -1
  48. data/lib/active_support/core_ext/marshal.rb +2 -0
  49. data/lib/active_support/core_ext/module.rb +0 -1
  50. data/lib/active_support/core_ext/module/attr_internal.rb +2 -2
  51. data/lib/active_support/core_ext/module/attribute_accessors.rb +30 -39
  52. data/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb +17 -19
  53. data/lib/active_support/core_ext/module/concerning.rb +8 -2
  54. data/lib/active_support/core_ext/module/delegation.rb +76 -33
  55. data/lib/active_support/core_ext/module/introspection.rb +16 -15
  56. data/lib/active_support/core_ext/module/redefine_method.rb +8 -17
  57. data/lib/active_support/core_ext/name_error.rb +29 -2
  58. data/lib/active_support/core_ext/numeric.rb +0 -1
  59. data/lib/active_support/core_ext/numeric/conversions.rb +129 -129
  60. data/lib/active_support/core_ext/object/blank.rb +1 -2
  61. data/lib/active_support/core_ext/object/deep_dup.rb +1 -1
  62. data/lib/active_support/core_ext/object/duplicable.rb +7 -114
  63. data/lib/active_support/core_ext/object/json.rb +14 -2
  64. data/lib/active_support/core_ext/object/try.rb +17 -7
  65. data/lib/active_support/core_ext/object/with_options.rb +1 -1
  66. data/lib/active_support/core_ext/range/compare_range.rb +34 -13
  67. data/lib/active_support/core_ext/range/conversions.rb +31 -29
  68. data/lib/active_support/core_ext/range/each.rb +0 -1
  69. data/lib/active_support/core_ext/range/include_time_with_zone.rb +8 -3
  70. data/lib/active_support/core_ext/regexp.rb +8 -5
  71. data/lib/active_support/core_ext/securerandom.rb +23 -3
  72. data/lib/active_support/core_ext/string/access.rb +5 -16
  73. data/lib/active_support/core_ext/string/conversions.rb +1 -0
  74. data/lib/active_support/core_ext/string/filters.rb +42 -1
  75. data/lib/active_support/core_ext/string/inflections.rb +45 -6
  76. data/lib/active_support/core_ext/string/inquiry.rb +1 -0
  77. data/lib/active_support/core_ext/string/multibyte.rb +6 -5
  78. data/lib/active_support/core_ext/string/output_safety.rb +70 -13
  79. data/lib/active_support/core_ext/string/starts_ends_with.rb +2 -2
  80. data/lib/active_support/core_ext/string/strip.rb +3 -1
  81. data/lib/active_support/core_ext/symbol.rb +3 -0
  82. data/lib/active_support/core_ext/symbol/starts_ends_with.rb +14 -0
  83. data/lib/active_support/core_ext/time/calculations.rb +50 -3
  84. data/lib/active_support/core_ext/time/conversions.rb +2 -0
  85. data/lib/active_support/core_ext/uri.rb +6 -1
  86. data/lib/active_support/current_attributes.rb +15 -2
  87. data/lib/active_support/current_attributes/test_helper.rb +13 -0
  88. data/lib/active_support/dependencies.rb +109 -34
  89. data/lib/active_support/dependencies/zeitwerk_integration.rb +117 -0
  90. data/lib/active_support/deprecation.rb +6 -1
  91. data/lib/active_support/deprecation/behaviors.rb +16 -3
  92. data/lib/active_support/deprecation/disallowed.rb +56 -0
  93. data/lib/active_support/deprecation/instance_delegator.rb +0 -1
  94. data/lib/active_support/deprecation/method_wrappers.rb +18 -23
  95. data/lib/active_support/deprecation/proxy_wrappers.rb +29 -6
  96. data/lib/active_support/deprecation/reporting.rb +50 -7
  97. data/lib/active_support/descendants_tracker.rb +59 -9
  98. data/lib/active_support/duration.rb +90 -38
  99. data/lib/active_support/duration/iso8601_parser.rb +2 -4
  100. data/lib/active_support/duration/iso8601_serializer.rb +18 -14
  101. data/lib/active_support/encrypted_configuration.rb +0 -4
  102. data/lib/active_support/encrypted_file.rb +22 -4
  103. data/lib/active_support/environment_inquirer.rb +20 -0
  104. data/lib/active_support/evented_file_update_checker.rb +82 -117
  105. data/lib/active_support/execution_wrapper.rb +1 -0
  106. data/lib/active_support/file_update_checker.rb +0 -1
  107. data/lib/active_support/fork_tracker.rb +62 -0
  108. data/lib/active_support/gem_version.rb +4 -4
  109. data/lib/active_support/hash_with_indifferent_access.rb +64 -41
  110. data/lib/active_support/i18n.rb +1 -0
  111. data/lib/active_support/i18n_railtie.rb +15 -8
  112. data/lib/active_support/inflector/inflections.rb +2 -7
  113. data/lib/active_support/inflector/methods.rb +49 -58
  114. data/lib/active_support/inflector/transliterate.rb +47 -18
  115. data/lib/active_support/json/decoding.rb +25 -26
  116. data/lib/active_support/json/encoding.rb +11 -3
  117. data/lib/active_support/key_generator.rb +1 -33
  118. data/lib/active_support/lazy_load_hooks.rb +5 -2
  119. data/lib/active_support/locale/en.rb +33 -0
  120. data/lib/active_support/locale/en.yml +7 -3
  121. data/lib/active_support/log_subscriber.rb +39 -9
  122. data/lib/active_support/logger.rb +2 -17
  123. data/lib/active_support/logger_silence.rb +11 -19
  124. data/lib/active_support/logger_thread_safe_level.rb +50 -6
  125. data/lib/active_support/message_encryptor.rb +8 -13
  126. data/lib/active_support/message_verifier.rb +10 -10
  127. data/lib/active_support/messages/metadata.rb +11 -2
  128. data/lib/active_support/messages/rotation_configuration.rb +2 -1
  129. data/lib/active_support/messages/rotator.rb +10 -9
  130. data/lib/active_support/multibyte/chars.rb +10 -68
  131. data/lib/active_support/multibyte/unicode.rb +15 -327
  132. data/lib/active_support/notifications.rb +72 -8
  133. data/lib/active_support/notifications/fanout.rb +116 -16
  134. data/lib/active_support/notifications/instrumenter.rb +71 -9
  135. data/lib/active_support/number_helper.rb +38 -12
  136. data/lib/active_support/number_helper/number_converter.rb +5 -6
  137. data/lib/active_support/number_helper/number_to_currency_converter.rb +4 -9
  138. data/lib/active_support/number_helper/number_to_delimited_converter.rb +3 -2
  139. data/lib/active_support/number_helper/number_to_human_converter.rb +4 -3
  140. data/lib/active_support/number_helper/number_to_human_size_converter.rb +4 -3
  141. data/lib/active_support/number_helper/number_to_percentage_converter.rb +3 -1
  142. data/lib/active_support/number_helper/number_to_phone_converter.rb +2 -1
  143. data/lib/active_support/number_helper/number_to_rounded_converter.rb +8 -7
  144. data/lib/active_support/number_helper/rounding_helper.rb +12 -28
  145. data/lib/active_support/option_merger.rb +22 -3
  146. data/lib/active_support/ordered_hash.rb +1 -1
  147. data/lib/active_support/ordered_options.rb +13 -3
  148. data/lib/active_support/parameter_filter.rb +133 -0
  149. data/lib/active_support/per_thread_registry.rb +1 -1
  150. data/lib/active_support/rails.rb +1 -10
  151. data/lib/active_support/railtie.rb +23 -1
  152. data/lib/active_support/reloader.rb +4 -5
  153. data/lib/active_support/rescuable.rb +4 -4
  154. data/lib/active_support/secure_compare_rotator.rb +51 -0
  155. data/lib/active_support/security_utils.rb +19 -12
  156. data/lib/active_support/string_inquirer.rb +4 -3
  157. data/lib/active_support/subscriber.rb +72 -28
  158. data/lib/active_support/tagged_logging.rb +42 -8
  159. data/lib/active_support/test_case.rb +91 -0
  160. data/lib/active_support/testing/assertions.rb +30 -9
  161. data/lib/active_support/testing/deprecation.rb +0 -1
  162. data/lib/active_support/testing/file_fixtures.rb +2 -0
  163. data/lib/active_support/testing/isolation.rb +2 -2
  164. data/lib/active_support/testing/method_call_assertions.rb +28 -1
  165. data/lib/active_support/testing/parallelization.rb +51 -0
  166. data/lib/active_support/testing/parallelization/server.rb +78 -0
  167. data/lib/active_support/testing/parallelization/worker.rb +100 -0
  168. data/lib/active_support/testing/stream.rb +1 -2
  169. data/lib/active_support/testing/time_helpers.rb +47 -12
  170. data/lib/active_support/time_with_zone.rb +81 -47
  171. data/lib/active_support/values/time_zone.rb +32 -17
  172. data/lib/active_support/xml_mini.rb +2 -10
  173. data/lib/active_support/xml_mini/jdom.rb +2 -3
  174. data/lib/active_support/xml_mini/libxml.rb +2 -2
  175. data/lib/active_support/xml_mini/libxmlsax.rb +4 -4
  176. data/lib/active_support/xml_mini/nokogiri.rb +2 -2
  177. data/lib/active_support/xml_mini/nokogirisax.rb +3 -3
  178. data/lib/active_support/xml_mini/rexml.rb +10 -3
  179. metadata +58 -32
  180. data/lib/active_support/core_ext/array/prepend_and_append.rb +0 -9
  181. data/lib/active_support/core_ext/hash/compact.rb +0 -29
  182. data/lib/active_support/core_ext/hash/transform_values.rb +0 -32
  183. data/lib/active_support/core_ext/kernel/agnostics.rb +0 -13
  184. data/lib/active_support/core_ext/module/reachable.rb +0 -11
  185. data/lib/active_support/core_ext/numeric/inquiry.rb +0 -28
  186. data/lib/active_support/core_ext/range/include_range.rb +0 -3
  187. data/lib/active_support/values/unicode_tables.dat +0 -0
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "weakref"
4
+
3
5
  module ActiveSupport
4
6
  # This module provides an internal implementation to track descendants
5
7
  # which is faster than iterating through ObjectSpace.
@@ -8,8 +10,10 @@ module ActiveSupport
8
10
 
9
11
  class << self
10
12
  def direct_descendants(klass)
11
- @@direct_descendants[klass] || []
13
+ descendants = @@direct_descendants[klass]
14
+ descendants ? descendants.to_a : []
12
15
  end
16
+ alias_method :subclasses, :direct_descendants
13
17
 
14
18
  def descendants(klass)
15
19
  arr = []
@@ -20,10 +24,10 @@ module ActiveSupport
20
24
  def clear
21
25
  if defined? ActiveSupport::Dependencies
22
26
  @@direct_descendants.each do |klass, descendants|
23
- if ActiveSupport::Dependencies.autoloaded?(klass)
27
+ if Dependencies.autoloaded?(klass)
24
28
  @@direct_descendants.delete(klass)
25
29
  else
26
- descendants.reject! { |v| ActiveSupport::Dependencies.autoloaded?(v) }
30
+ descendants.reject! { |v| Dependencies.autoloaded?(v) }
27
31
  end
28
32
  end
29
33
  else
@@ -34,16 +38,18 @@ module ActiveSupport
34
38
  # This is the only method that is not thread safe, but is only ever called
35
39
  # during the eager loading phase.
36
40
  def store_inherited(klass, descendant)
37
- (@@direct_descendants[klass] ||= []) << descendant
41
+ (@@direct_descendants[klass] ||= DescendantsArray.new) << descendant
38
42
  end
39
43
 
40
44
  private
41
- def accumulate_descendants(klass, acc)
42
- if direct_descendants = @@direct_descendants[klass]
43
- acc.concat(direct_descendants)
44
- direct_descendants.each { |direct_descendant| accumulate_descendants(direct_descendant, acc) }
45
+ def accumulate_descendants(klass, acc)
46
+ if direct_descendants = @@direct_descendants[klass]
47
+ direct_descendants.each do |direct_descendant|
48
+ acc << direct_descendant
49
+ accumulate_descendants(direct_descendant, acc)
50
+ end
51
+ end
45
52
  end
46
- end
47
53
  end
48
54
 
49
55
  def inherited(base)
@@ -54,9 +60,53 @@ module ActiveSupport
54
60
  def direct_descendants
55
61
  DescendantsTracker.direct_descendants(self)
56
62
  end
63
+ alias_method :subclasses, :direct_descendants
57
64
 
58
65
  def descendants
59
66
  DescendantsTracker.descendants(self)
60
67
  end
68
+
69
+ # DescendantsArray is an array that contains weak references to classes.
70
+ class DescendantsArray # :nodoc:
71
+ include Enumerable
72
+
73
+ def initialize
74
+ @refs = []
75
+ end
76
+
77
+ def initialize_copy(orig)
78
+ @refs = @refs.dup
79
+ end
80
+
81
+ def <<(klass)
82
+ @refs << WeakRef.new(klass)
83
+ end
84
+
85
+ def each
86
+ @refs.reject! do |ref|
87
+ yield ref.__getobj__
88
+ false
89
+ rescue WeakRef::RefError
90
+ true
91
+ end
92
+ self
93
+ end
94
+
95
+ def refs_size
96
+ @refs.size
97
+ end
98
+
99
+ def cleanup!
100
+ @refs.delete_if { |ref| !ref.weakref_alive? }
101
+ end
102
+
103
+ def reject!
104
+ @refs.reject! do |ref|
105
+ yield ref.__getobj__
106
+ rescue WeakRef::RefError
107
+ true
108
+ end
109
+ end
110
+ end
61
111
  end
62
112
  end
@@ -4,7 +4,6 @@ require "active_support/core_ext/array/conversions"
4
4
  require "active_support/core_ext/module/delegation"
5
5
  require "active_support/core_ext/object/acts_like"
6
6
  require "active_support/core_ext/string/filters"
7
- require "active_support/deprecation"
8
7
 
9
8
  module ActiveSupport
10
9
  # Provides accurate date and time measurements using Date#advance and
@@ -40,7 +39,7 @@ module ActiveSupport
40
39
 
41
40
  def +(other)
42
41
  if Duration === other
43
- seconds = value + other.parts[:seconds]
42
+ seconds = value + other.parts.fetch(:seconds, 0)
44
43
  new_parts = other.parts.merge(seconds: seconds)
45
44
  new_value = value + other.value
46
45
 
@@ -52,8 +51,8 @@ module ActiveSupport
52
51
 
53
52
  def -(other)
54
53
  if Duration === other
55
- seconds = value - other.parts[:seconds]
56
- new_parts = other.parts.map { |part, other_value| [part, -other_value] }.to_h
54
+ seconds = value - other.parts.fetch(:seconds, 0)
55
+ new_parts = other.parts.transform_values(&:-@)
57
56
  new_parts = new_parts.merge(seconds: seconds)
58
57
  new_value = value - other.value
59
58
 
@@ -65,7 +64,7 @@ module ActiveSupport
65
64
 
66
65
  def *(other)
67
66
  if Duration === other
68
- new_parts = other.parts.map { |part, other_value| [part, value * other_value] }.to_h
67
+ new_parts = other.parts.transform_values { |other_value| value * other_value }
69
68
  new_value = value * other.value
70
69
 
71
70
  Duration.new(new_value, new_parts)
@@ -148,31 +147,31 @@ module ActiveSupport
148
147
  end
149
148
 
150
149
  def seconds(value) #:nodoc:
151
- new(value, [[:seconds, value]])
150
+ new(value, seconds: value)
152
151
  end
153
152
 
154
153
  def minutes(value) #:nodoc:
155
- new(value * SECONDS_PER_MINUTE, [[:minutes, value]])
154
+ new(value * SECONDS_PER_MINUTE, minutes: value)
156
155
  end
157
156
 
158
157
  def hours(value) #:nodoc:
159
- new(value * SECONDS_PER_HOUR, [[:hours, value]])
158
+ new(value * SECONDS_PER_HOUR, hours: value)
160
159
  end
161
160
 
162
161
  def days(value) #:nodoc:
163
- new(value * SECONDS_PER_DAY, [[:days, value]])
162
+ new(value * SECONDS_PER_DAY, days: value)
164
163
  end
165
164
 
166
165
  def weeks(value) #:nodoc:
167
- new(value * SECONDS_PER_WEEK, [[:weeks, value]])
166
+ new(value * SECONDS_PER_WEEK, weeks: value)
168
167
  end
169
168
 
170
169
  def months(value) #:nodoc:
171
- new(value * SECONDS_PER_MONTH, [[:months, value]])
170
+ new(value * SECONDS_PER_MONTH, months: value)
172
171
  end
173
172
 
174
173
  def years(value) #:nodoc:
175
- new(value * SECONDS_PER_YEAR, [[:years, value]])
174
+ new(value * SECONDS_PER_YEAR, years: value)
176
175
  end
177
176
 
178
177
  # Creates a new Duration from a seconds value that is converted
@@ -182,16 +181,20 @@ module ActiveSupport
182
181
  # ActiveSupport::Duration.build(2716146).parts # => {:months=>1, :days=>1}
183
182
  #
184
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
+
185
188
  parts = {}
186
- remainder = value.to_f
189
+ remainder = value.round(9)
187
190
 
188
191
  PARTS.each do |part|
189
192
  unless part == :seconds
190
193
  part_in_seconds = PARTS_IN_SECONDS[part]
191
194
  parts[part] = remainder.div(part_in_seconds)
192
- remainder = (remainder % part_in_seconds).round(9)
195
+ remainder %= part_in_seconds
193
196
  end
194
- end
197
+ end unless value == 0
195
198
 
196
199
  parts[:seconds] = remainder
197
200
 
@@ -199,7 +202,6 @@ module ActiveSupport
199
202
  end
200
203
 
201
204
  private
202
-
203
205
  def calculate_total_seconds(parts)
204
206
  parts.inject(0) do |total, (part, value)|
205
207
  total + value * PARTS_IN_SECONDS[part]
@@ -208,14 +210,16 @@ module ActiveSupport
208
210
  end
209
211
 
210
212
  def initialize(value, parts) #:nodoc:
211
- @value, @parts = value, parts.to_h
212
- @parts.default = 0
213
- @parts.reject! { |k, v| v.zero? }
213
+ @value, @parts = value, parts
214
+ @parts.reject! { |k, v| v.zero? } unless value == 0
214
215
  end
215
216
 
216
217
  def coerce(other) #:nodoc:
217
- if Scalar === other
218
+ case other
219
+ when Scalar
218
220
  [other, self]
221
+ when Duration
222
+ [Scalar.new(other.value), self]
219
223
  else
220
224
  [Scalar.new(other), self]
221
225
  end
@@ -235,13 +239,12 @@ module ActiveSupport
235
239
  # are treated as seconds.
236
240
  def +(other)
237
241
  if Duration === other
238
- parts = @parts.dup
239
- other.parts.each do |(key, value)|
240
- parts[key] += value
242
+ parts = @parts.merge(other.parts) do |_key, value, other_value|
243
+ value + other_value
241
244
  end
242
245
  Duration.new(value + other.value, parts)
243
246
  else
244
- seconds = @parts[:seconds] + other
247
+ seconds = @parts.fetch(:seconds, 0) + other
245
248
  Duration.new(value + other, @parts.merge(seconds: seconds))
246
249
  end
247
250
  end
@@ -255,9 +258,9 @@ module ActiveSupport
255
258
  # Multiplies this Duration by a Numeric and returns a new Duration.
256
259
  def *(other)
257
260
  if Scalar === other || Duration === other
258
- Duration.new(value * other.value, parts.map { |type, number| [type, number * other.value] })
261
+ Duration.new(value * other.value, parts.transform_values { |number| number * other.value })
259
262
  elsif Numeric === other
260
- Duration.new(value * other, parts.map { |type, number| [type, number * other] })
263
+ Duration.new(value * other, parts.transform_values { |number| number * other })
261
264
  else
262
265
  raise_type_error(other)
263
266
  end
@@ -266,11 +269,11 @@ module ActiveSupport
266
269
  # Divides this Duration by a Numeric and returns a new Duration.
267
270
  def /(other)
268
271
  if Scalar === other
269
- Duration.new(value / other.value, parts.map { |type, number| [type, number / other.value] })
272
+ Duration.new(value / other.value, parts.transform_values { |number| number / other.value })
270
273
  elsif Duration === other
271
274
  value / other.value
272
275
  elsif Numeric === other
273
- Duration.new(value / other, parts.map { |type, number| [type, number / other] })
276
+ Duration.new(value / other, parts.transform_values { |number| number / other })
274
277
  else
275
278
  raise_type_error(other)
276
279
  end
@@ -289,7 +292,11 @@ module ActiveSupport
289
292
  end
290
293
 
291
294
  def -@ #:nodoc:
292
- Duration.new(-value, parts.map { |type, number| [type, -number] })
295
+ Duration.new(-value, parts.transform_values(&:-@))
296
+ end
297
+
298
+ def +@ #:nodoc:
299
+ self
293
300
  end
294
301
 
295
302
  def is_a?(klass) #:nodoc:
@@ -336,12 +343,55 @@ module ActiveSupport
336
343
  # 1.year.to_i # => 31556952
337
344
  #
338
345
  # In such cases, Ruby's core
339
- # Date[http://ruby-doc.org/stdlib/libdoc/date/rdoc/Date.html] and
340
- # 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
341
348
  # date and time arithmetic.
342
349
  def to_i
343
350
  @value.to_i
344
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
345
395
 
346
396
  # Returns +true+ if +other+ is also a Duration instance, which has the
347
397
  # same parts as this one.
@@ -370,10 +420,9 @@ module ActiveSupport
370
420
  alias :before :ago
371
421
 
372
422
  def inspect #:nodoc:
373
- return "0 seconds" if parts.empty?
423
+ return "#{value} seconds" if parts.empty?
374
424
 
375
425
  parts.
376
- reduce(::Hash.new(0)) { |h, (l, r)| h[l] += r; h }.
377
426
  sort_by { |unit, _ | PARTS.index(unit) }.
378
427
  map { |unit, val| "#{val} #{val == 1 ? unit.to_s.chop : unit.to_s}" }.
379
428
  to_sentence(locale: ::I18n.default_locale)
@@ -398,10 +447,15 @@ module ActiveSupport
398
447
  end
399
448
 
400
449
  private
401
-
402
450
  def sum(sign, time = ::Time.current)
403
- parts.inject(time) do |t, (type, number)|
404
- if t.acts_like?(:time) || t.acts_like?(:date)
451
+ unless time.acts_like?(:time) || time.acts_like?(:date)
452
+ raise ::ArgumentError, "expected a time or date, got #{time.inspect}"
453
+ end
454
+
455
+ if parts.empty?
456
+ time.since(sign * value)
457
+ else
458
+ parts.inject(time) do |t, (type, number)|
405
459
  if type == :seconds
406
460
  t.since(sign * number)
407
461
  elsif type == :minutes
@@ -411,8 +465,6 @@ module ActiveSupport
411
465
  else
412
466
  t.advance(type => sign * number)
413
467
  end
414
- else
415
- raise ::ArgumentError, "expected a time or date, got #{time.inspect}"
416
468
  end
417
469
  end
418
470
  end
@@ -1,7 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "strscan"
4
- require "active_support/core_ext/regexp"
5
4
 
6
5
  module ActiveSupport
7
6
  class Duration
@@ -14,8 +13,8 @@ module ActiveSupport
14
13
  class ParsingError < ::ArgumentError; end
15
14
 
16
15
  PERIOD_OR_COMMA = /\.|,/
17
- PERIOD = ".".freeze
18
- COMMA = ",".freeze
16
+ PERIOD = "."
17
+ COMMA = ","
19
18
 
20
19
  SIGN_MARKER = /\A\-|\+|/
21
20
  DATE_MARKER = /P/
@@ -81,7 +80,6 @@ module ActiveSupport
81
80
  end
82
81
 
83
82
  private
84
-
85
83
  def finished?
86
84
  scanner.eos?
87
85
  end
@@ -1,12 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "active_support/core_ext/object/blank"
4
- require "active_support/core_ext/hash/transform_values"
5
4
 
6
5
  module ActiveSupport
7
6
  class Duration
8
7
  # Serializes duration to string according to ISO 8601 Duration format.
9
8
  class ISO8601Serializer # :nodoc:
9
+ DATE_COMPONENTS = %i(years months days)
10
+
10
11
  def initialize(duration, precision: nil)
11
12
  @duration = duration
12
13
  @precision = precision
@@ -14,26 +15,25 @@ module ActiveSupport
14
15
 
15
16
  # Builds and returns output string.
16
17
  def serialize
17
- parts, sign = normalize
18
- return "PT0S".freeze if parts.empty?
18
+ parts = normalize
19
+ return "PT0S" if parts.empty?
19
20
 
20
- output = "P".dup
21
+ output = +"P"
21
22
  output << "#{parts[:years]}Y" if parts.key?(:years)
22
23
  output << "#{parts[:months]}M" if parts.key?(:months)
23
- output << "#{parts[:weeks]}W" if parts.key?(:weeks)
24
24
  output << "#{parts[:days]}D" if parts.key?(:days)
25
- time = "".dup
25
+ output << "#{parts[:weeks]}W" if parts.key?(:weeks)
26
+ time = +""
26
27
  time << "#{parts[:hours]}H" if parts.key?(:hours)
27
28
  time << "#{parts[:minutes]}M" if parts.key?(:minutes)
28
29
  if parts.key?(:seconds)
29
30
  time << "#{sprintf(@precision ? "%0.0#{@precision}f" : '%g', parts[:seconds])}S"
30
31
  end
31
32
  output << "T#{time}" unless time.empty?
32
- "#{sign}#{output}"
33
+ output
33
34
  end
34
35
 
35
36
  private
36
-
37
37
  # Return pair of duration's parts and whole duration sign.
38
38
  # Parts are summarized (as they can become repetitive due to addition, etc).
39
39
  # Zero parts are removed as not significant.
@@ -42,13 +42,17 @@ module ActiveSupport
42
42
  parts = @duration.parts.each_with_object(Hash.new(0)) do |(k, v), p|
43
43
  p[k] += v unless v.zero?
44
44
  end
45
- # If all parts are negative - let's make a negative duration
46
- sign = ""
47
- if parts.values.all? { |v| v < 0 }
48
- sign = "-"
49
- parts.transform_values!(&:-@)
45
+
46
+ # Convert weeks to days and remove weeks if mixed with date parts
47
+ if week_mixed_with_date?(parts)
48
+ parts[:days] += parts.delete(:weeks) * SECONDS_PER_WEEK / SECONDS_PER_DAY
50
49
  end
51
- [parts, sign]
50
+
51
+ parts
52
+ end
53
+
54
+ def week_mixed_with_date?(parts)
55
+ parts.key?(:weeks) && (parts.keys & DATE_COMPONENTS).any?
52
56
  end
53
57
  end
54
58
  end