activesupport 5.2.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 (190) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +362 -333
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +4 -3
  5. data/lib/active_support/actionable_error.rb +48 -0
  6. data/lib/active_support/array_inquirer.rb +4 -2
  7. data/lib/active_support/backtrace_cleaner.rb +29 -3
  8. data/lib/active_support/benchmarkable.rb +1 -1
  9. data/lib/active_support/cache/file_store.rb +33 -33
  10. data/lib/active_support/cache/mem_cache_store.rb +31 -29
  11. data/lib/active_support/cache/memory_store.rb +59 -33
  12. data/lib/active_support/cache/null_store.rb +8 -3
  13. data/lib/active_support/cache/redis_cache_store.rb +84 -45
  14. data/lib/active_support/cache/strategy/local_cache.rb +41 -26
  15. data/lib/active_support/cache.rb +174 -113
  16. data/lib/active_support/callbacks.rb +81 -64
  17. data/lib/active_support/concern.rb +76 -5
  18. data/lib/active_support/concurrency/load_interlock_aware_monitor.rb +18 -0
  19. data/lib/active_support/concurrency/share_lock.rb +0 -1
  20. data/lib/active_support/configurable.rb +10 -14
  21. data/lib/active_support/configuration_file.rb +46 -0
  22. data/lib/active_support/core_ext/array/access.rb +18 -6
  23. data/lib/active_support/core_ext/array/conversions.rb +5 -5
  24. data/lib/active_support/core_ext/array/extract.rb +21 -0
  25. data/lib/active_support/core_ext/array.rb +1 -1
  26. data/lib/active_support/core_ext/benchmark.rb +2 -2
  27. data/lib/active_support/core_ext/class/attribute.rb +32 -47
  28. data/lib/active_support/core_ext/class/subclasses.rb +17 -38
  29. data/lib/active_support/core_ext/date/calculations.rb +6 -5
  30. data/lib/active_support/core_ext/date/conversions.rb +2 -1
  31. data/lib/active_support/core_ext/date_and_time/calculations.rb +37 -47
  32. data/lib/active_support/core_ext/date_and_time/compatibility.rb +15 -0
  33. data/lib/active_support/core_ext/date_and_time/zones.rb +0 -1
  34. data/lib/active_support/core_ext/date_time/calculations.rb +1 -1
  35. data/lib/active_support/core_ext/date_time/conversions.rb +0 -1
  36. data/lib/active_support/core_ext/digest.rb +3 -0
  37. data/lib/active_support/core_ext/enumerable.rb +171 -70
  38. data/lib/active_support/core_ext/file/atomic.rb +1 -1
  39. data/lib/active_support/core_ext/hash/conversions.rb +3 -3
  40. data/lib/active_support/core_ext/hash/deep_transform_values.rb +46 -0
  41. data/lib/active_support/core_ext/hash/except.rb +2 -2
  42. data/lib/active_support/core_ext/hash/keys.rb +1 -30
  43. data/lib/active_support/core_ext/hash/slice.rb +6 -27
  44. data/lib/active_support/core_ext/hash.rb +1 -2
  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/attr_internal.rb +2 -2
  50. data/lib/active_support/core_ext/module/attribute_accessors.rb +30 -39
  51. data/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb +17 -19
  52. data/lib/active_support/core_ext/module/concerning.rb +8 -2
  53. data/lib/active_support/core_ext/module/delegation.rb +76 -33
  54. data/lib/active_support/core_ext/module/introspection.rb +16 -15
  55. data/lib/active_support/core_ext/module/redefine_method.rb +8 -17
  56. data/lib/active_support/core_ext/module.rb +0 -1
  57. data/lib/active_support/core_ext/name_error.rb +29 -2
  58. data/lib/active_support/core_ext/numeric/conversions.rb +129 -129
  59. data/lib/active_support/core_ext/numeric.rb +0 -1
  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 +7 -2
  64. data/lib/active_support/core_ext/object/to_query.rb +5 -2
  65. data/lib/active_support/core_ext/object/try.rb +17 -7
  66. data/lib/active_support/core_ext/object/with_options.rb +1 -1
  67. data/lib/active_support/core_ext/range/compare_range.rb +82 -0
  68. data/lib/active_support/core_ext/range/conversions.rb +31 -29
  69. data/lib/active_support/core_ext/range/each.rb +0 -1
  70. data/lib/active_support/core_ext/range/include_time_with_zone.rb +8 -3
  71. data/lib/active_support/core_ext/range.rb +1 -1
  72. data/lib/active_support/core_ext/regexp.rb +8 -5
  73. data/lib/active_support/core_ext/securerandom.rb +23 -3
  74. data/lib/active_support/core_ext/string/access.rb +5 -16
  75. data/lib/active_support/core_ext/string/conversions.rb +1 -0
  76. data/lib/active_support/core_ext/string/filters.rb +42 -1
  77. data/lib/active_support/core_ext/string/inflections.rb +45 -6
  78. data/lib/active_support/core_ext/string/inquiry.rb +1 -0
  79. data/lib/active_support/core_ext/string/multibyte.rb +6 -5
  80. data/lib/active_support/core_ext/string/output_safety.rb +69 -12
  81. data/lib/active_support/core_ext/string/starts_ends_with.rb +2 -2
  82. data/lib/active_support/core_ext/string/strip.rb +3 -1
  83. data/lib/active_support/core_ext/symbol/starts_ends_with.rb +14 -0
  84. data/lib/active_support/core_ext/symbol.rb +3 -0
  85. data/lib/active_support/core_ext/time/calculations.rb +50 -3
  86. data/lib/active_support/core_ext/time/conversions.rb +1 -0
  87. data/lib/active_support/core_ext/uri.rb +7 -5
  88. data/lib/active_support/current_attributes/test_helper.rb +13 -0
  89. data/lib/active_support/current_attributes.rb +15 -2
  90. data/lib/active_support/dependencies/zeitwerk_integration.rb +117 -0
  91. data/lib/active_support/dependencies.rb +118 -35
  92. data/lib/active_support/deprecation/behaviors.rb +20 -3
  93. data/lib/active_support/deprecation/disallowed.rb +56 -0
  94. data/lib/active_support/deprecation/instance_delegator.rb +0 -1
  95. data/lib/active_support/deprecation/method_wrappers.rb +21 -13
  96. data/lib/active_support/deprecation/proxy_wrappers.rb +29 -6
  97. data/lib/active_support/deprecation/reporting.rb +51 -8
  98. data/lib/active_support/deprecation.rb +6 -1
  99. data/lib/active_support/descendants_tracker.rb +59 -9
  100. data/lib/active_support/duration/iso8601_parser.rb +2 -4
  101. data/lib/active_support/duration/iso8601_serializer.rb +18 -14
  102. data/lib/active_support/duration.rb +90 -38
  103. data/lib/active_support/encrypted_configuration.rb +1 -5
  104. data/lib/active_support/encrypted_file.rb +23 -5
  105. data/lib/active_support/environment_inquirer.rb +20 -0
  106. data/lib/active_support/evented_file_update_checker.rb +82 -117
  107. data/lib/active_support/execution_wrapper.rb +1 -0
  108. data/lib/active_support/file_update_checker.rb +0 -1
  109. data/lib/active_support/fork_tracker.rb +62 -0
  110. data/lib/active_support/gem_version.rb +2 -2
  111. data/lib/active_support/hash_with_indifferent_access.rb +78 -41
  112. data/lib/active_support/i18n.rb +1 -0
  113. data/lib/active_support/i18n_railtie.rb +16 -5
  114. data/lib/active_support/inflector/inflections.rb +2 -7
  115. data/lib/active_support/inflector/methods.rb +50 -57
  116. data/lib/active_support/inflector/transliterate.rb +47 -18
  117. data/lib/active_support/json/decoding.rb +25 -26
  118. data/lib/active_support/json/encoding.rb +11 -3
  119. data/lib/active_support/key_generator.rb +1 -33
  120. data/lib/active_support/lazy_load_hooks.rb +5 -2
  121. data/lib/active_support/locale/en.rb +33 -0
  122. data/lib/active_support/locale/en.yml +7 -3
  123. data/lib/active_support/log_subscriber.rb +39 -9
  124. data/lib/active_support/logger.rb +2 -17
  125. data/lib/active_support/logger_silence.rb +11 -19
  126. data/lib/active_support/logger_thread_safe_level.rb +52 -7
  127. data/lib/active_support/message_encryptor.rb +8 -13
  128. data/lib/active_support/message_verifier.rb +10 -10
  129. data/lib/active_support/messages/metadata.rb +11 -2
  130. data/lib/active_support/messages/rotation_configuration.rb +2 -1
  131. data/lib/active_support/messages/rotator.rb +10 -9
  132. data/lib/active_support/multibyte/chars.rb +10 -68
  133. data/lib/active_support/multibyte/unicode.rb +15 -327
  134. data/lib/active_support/notifications/fanout.rb +116 -16
  135. data/lib/active_support/notifications/instrumenter.rb +71 -9
  136. data/lib/active_support/notifications.rb +72 -8
  137. data/lib/active_support/number_helper/number_converter.rb +5 -6
  138. data/lib/active_support/number_helper/number_to_currency_converter.rb +4 -9
  139. data/lib/active_support/number_helper/number_to_delimited_converter.rb +3 -2
  140. data/lib/active_support/number_helper/number_to_human_converter.rb +4 -3
  141. data/lib/active_support/number_helper/number_to_human_size_converter.rb +4 -3
  142. data/lib/active_support/number_helper/number_to_percentage_converter.rb +3 -1
  143. data/lib/active_support/number_helper/number_to_phone_converter.rb +2 -1
  144. data/lib/active_support/number_helper/number_to_rounded_converter.rb +8 -7
  145. data/lib/active_support/number_helper/rounding_helper.rb +12 -28
  146. data/lib/active_support/number_helper.rb +38 -12
  147. data/lib/active_support/option_merger.rb +22 -3
  148. data/lib/active_support/ordered_hash.rb +1 -1
  149. data/lib/active_support/ordered_options.rb +13 -3
  150. data/lib/active_support/parameter_filter.rb +133 -0
  151. data/lib/active_support/per_thread_registry.rb +1 -1
  152. data/lib/active_support/rails.rb +1 -10
  153. data/lib/active_support/railtie.rb +23 -1
  154. data/lib/active_support/reloader.rb +4 -5
  155. data/lib/active_support/secure_compare_rotator.rb +51 -0
  156. data/lib/active_support/security_utils.rb +19 -12
  157. data/lib/active_support/string_inquirer.rb +4 -3
  158. data/lib/active_support/subscriber.rb +72 -24
  159. data/lib/active_support/tagged_logging.rb +42 -8
  160. data/lib/active_support/test_case.rb +92 -1
  161. data/lib/active_support/testing/assertions.rb +30 -9
  162. data/lib/active_support/testing/deprecation.rb +0 -1
  163. data/lib/active_support/testing/file_fixtures.rb +2 -0
  164. data/lib/active_support/testing/isolation.rb +2 -2
  165. data/lib/active_support/testing/method_call_assertions.rb +28 -1
  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/parallelization.rb +51 -0
  169. data/lib/active_support/testing/setup_and_teardown.rb +5 -9
  170. data/lib/active_support/testing/stream.rb +1 -2
  171. data/lib/active_support/testing/time_helpers.rb +47 -12
  172. data/lib/active_support/time_with_zone.rb +81 -47
  173. data/lib/active_support/values/time_zone.rb +34 -18
  174. data/lib/active_support/xml_mini/jdom.rb +2 -3
  175. data/lib/active_support/xml_mini/libxml.rb +2 -2
  176. data/lib/active_support/xml_mini/libxmlsax.rb +4 -4
  177. data/lib/active_support/xml_mini/nokogiri.rb +2 -2
  178. data/lib/active_support/xml_mini/nokogirisax.rb +3 -3
  179. data/lib/active_support/xml_mini/rexml.rb +10 -3
  180. data/lib/active_support/xml_mini.rb +2 -10
  181. data/lib/active_support.rb +14 -1
  182. metadata +57 -30
  183. data/lib/active_support/core_ext/array/prepend_and_append.rb +0 -9
  184. data/lib/active_support/core_ext/hash/compact.rb +0 -29
  185. data/lib/active_support/core_ext/hash/transform_values.rb +0 -32
  186. data/lib/active_support/core_ext/kernel/agnostics.rb +0 -13
  187. data/lib/active_support/core_ext/module/reachable.rb +0 -11
  188. data/lib/active_support/core_ext/numeric/inquiry.rb +0 -28
  189. data/lib/active_support/core_ext/range/include_range.rb +0 -25
  190. data/lib/active_support/values/unicode_tables.dat +0 -0
@@ -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
@@ -38,12 +38,8 @@ module ActiveSupport
38
38
  @options ||= ActiveSupport::InheritableOptions.new(config)
39
39
  end
40
40
 
41
- def serialize(config)
42
- config.present? ? YAML.dump(config) : ""
43
- end
44
-
45
41
  def deserialize(config)
46
- config.present? ? YAML.load(config, content_path) : {}
42
+ YAML.load(config).presence || {}
47
43
  end
48
44
  end
49
45
  end
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "pathname"
4
+ require "tmpdir"
4
5
  require "active_support/message_encryptor"
5
6
 
6
7
  module ActiveSupport
@@ -19,17 +20,28 @@ module ActiveSupport
19
20
  end
20
21
  end
21
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
+
22
29
  CIPHER = "aes-128-gcm"
23
30
 
24
31
  def self.generate_key
25
32
  SecureRandom.hex(ActiveSupport::MessageEncryptor.key_len(CIPHER))
26
33
  end
27
34
 
35
+ def self.expected_key_length # :nodoc:
36
+ @expected_key_length ||= generate_key.length
37
+ end
38
+
28
39
 
29
40
  attr_reader :content_path, :key_path, :env_key, :raise_if_missing_key
30
41
 
31
42
  def initialize(content_path:, key_path:, env_key:, raise_if_missing_key:)
32
- @content_path, @key_path = Pathname.new(content_path), Pathname.new(key_path)
43
+ @content_path = Pathname.new(content_path).yield_self { |path| path.symlink? ? path.realpath : path }
44
+ @key_path = Pathname.new(key_path)
33
45
  @env_key, @raise_if_missing_key = env_key, raise_if_missing_key
34
46
  end
35
47
 
@@ -57,7 +69,7 @@ module ActiveSupport
57
69
 
58
70
  private
59
71
  def writing(contents)
60
- tmp_file = "#{content_path.basename}.#{Process.pid}"
72
+ tmp_file = "#{Process.pid}.#{content_path.basename.to_s.chomp('.enc')}"
61
73
  tmp_path = Pathname.new File.join(Dir.tmpdir, tmp_file)
62
74
  tmp_path.binwrite contents
63
75
 
@@ -67,11 +79,12 @@ module ActiveSupport
67
79
 
68
80
  write(updated_contents) if updated_contents != contents
69
81
  ensure
70
- FileUtils.rm(tmp_path) if tmp_path.exist?
82
+ FileUtils.rm(tmp_path) if tmp_path&.exist?
71
83
  end
72
84
 
73
85
 
74
86
  def encrypt(contents)
87
+ check_key_length
75
88
  encryptor.encrypt_and_sign contents
76
89
  end
77
90
 
@@ -89,11 +102,16 @@ module ActiveSupport
89
102
  end
90
103
 
91
104
  def read_key_file
92
- key_path.binread.strip if key_path.exist?
105
+ return @key_file_contents if defined?(@key_file_contents)
106
+ @key_file_contents = (key_path.binread.strip if key_path.exist?)
93
107
  end
94
108
 
95
109
  def handle_missing_key
96
- raise MissingKeyError, key_path: key_path, env_key: env_key if raise_if_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
97
115
  end
98
116
  end
99
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
@@ -3,6 +3,8 @@
3
3
  require "set"
4
4
  require "pathname"
5
5
  require "concurrent/atomic/atomic_boolean"
6
+ require "listen"
7
+ require "active_support/fork_tracker"
6
8
 
7
9
  module ActiveSupport
8
10
  # Allows you to "listen" to changes in a file system.
@@ -38,48 +40,22 @@ module ActiveSupport
38
40
  raise ArgumentError, "A block is required to initialize an EventedFileUpdateChecker"
39
41
  end
40
42
 
41
- @ph = PathHelper.new
42
- @files = files.map { |f| @ph.xpath(f) }.to_set
43
-
44
- @dirs = {}
45
- dirs.each do |dir, exts|
46
- @dirs[@ph.xpath(dir)] = Array(exts).map { |ext| @ph.normalize_extension(ext) }
47
- end
48
-
49
- @block = block
50
- @updated = Concurrent::AtomicBoolean.new(false)
51
- @lcsp = @ph.longest_common_subpath(@dirs.keys)
52
- @pid = Process.pid
53
- @boot_mutex = Mutex.new
54
-
55
- if (@dtw = directories_to_watch).any?
56
- # Loading listen triggers warnings. These are originated by a legit
57
- # usage of attr_* macros for private attributes, but adds a lot of noise
58
- # to our test suite. Thus, we lazy load it and disable warnings locally.
59
- silence_warnings do
60
- begin
61
- require "listen"
62
- rescue LoadError => e
63
- raise LoadError, "Could not load the 'listen' gem. Add `gem 'listen'` to the development group of your Gemfile", e.backtrace
64
- end
65
- end
66
- end
67
- boot!
43
+ @block = block
44
+ @core = Core.new(files, dirs)
45
+ ObjectSpace.define_finalizer(self, @core.finalizer)
68
46
  end
69
47
 
70
48
  def updated?
71
- @boot_mutex.synchronize do
72
- if @pid != Process.pid
73
- boot!
74
- @pid = Process.pid
75
- @updated.make_true
76
- end
49
+ if @core.restart?
50
+ @core.thread_safely(&:restart)
51
+ @core.updated.make_true
77
52
  end
78
- @updated.true?
53
+
54
+ @core.updated.true?
79
55
  end
80
56
 
81
57
  def execute
82
- @updated.make_false
58
+ @core.updated.make_false
83
59
  @block.call
84
60
  end
85
61
 
@@ -91,115 +67,104 @@ module ActiveSupport
91
67
  end
92
68
  end
93
69
 
94
- private
95
- def boot!
96
- Listen.to(*@dtw, &method(:changed)).start
97
- end
70
+ class Core
71
+ attr_reader :updated
98
72
 
99
- def changed(modified, added, removed)
100
- unless updated?
101
- @updated.make_true if (modified + added + removed).any? { |f| watching?(f) }
73
+ def initialize(files, dirs)
74
+ @files = files.map { |file| Pathname(file).expand_path }.to_set
75
+
76
+ @dirs = dirs.each_with_object({}) do |(dir, exts), hash|
77
+ hash[Pathname(dir).expand_path] = Array(exts).map { |ext| ext.to_s.sub(/\A\.?/, ".") }.to_set
102
78
  end
103
- end
104
79
 
105
- def watching?(file)
106
- file = @ph.xpath(file)
80
+ @common_path = common_path(@dirs.keys)
107
81
 
108
- if @files.member?(file)
109
- true
110
- elsif file.directory?
111
- false
112
- else
113
- ext = @ph.normalize_extension(file.extname)
82
+ @dtw = directories_to_watch
83
+ @missing = []
114
84
 
115
- file.dirname.ascend do |dir|
116
- if @dirs.fetch(dir, []).include?(ext)
117
- break true
118
- elsif dir == @lcsp || dir.root?
119
- break false
120
- end
121
- end
122
- end
85
+ @updated = Concurrent::AtomicBoolean.new(false)
86
+ @mutex = Mutex.new
87
+
88
+ start
89
+ @after_fork = ActiveSupport::ForkTracker.after_fork { start }
123
90
  end
124
91
 
125
- def directories_to_watch
126
- dtw = (@files + @dirs.keys).map { |f| @ph.existing_parent(f) }
127
- dtw.compact!
128
- dtw.uniq!
92
+ def finalizer
93
+ proc do
94
+ stop
95
+ ActiveSupport::ForkTracker.unregister(@after_fork)
96
+ end
97
+ end
129
98
 
130
- normalized_gem_paths = Gem.path.map { |path| File.join path, "" }
131
- dtw = dtw.reject do |path|
132
- normalized_gem_paths.any? { |gem_path| path.to_s.start_with?(gem_path) }
99
+ def thread_safely
100
+ @mutex.synchronize do
101
+ yield self
133
102
  end
103
+ end
134
104
 
135
- @ph.filter_out_descendants(dtw)
105
+ def start
106
+ normalize_dirs!
107
+ @dtw, @missing = [*@dtw, *@missing].partition(&:exist?)
108
+ @listener = @dtw.any? ? Listen.to(*@dtw, &method(:changed)) : nil
109
+ @listener&.start
136
110
  end
137
111
 
138
- class PathHelper
139
- def xpath(path)
140
- Pathname.new(path).expand_path
141
- end
112
+ def stop
113
+ @listener&.stop
114
+ end
142
115
 
143
- def normalize_extension(ext)
144
- ext.to_s.sub(/\A\./, "")
145
- end
116
+ def restart
117
+ stop
118
+ start
119
+ end
146
120
 
147
- # Given a collection of Pathname objects returns the longest subpath
148
- # common to all of them, or +nil+ if there is none.
149
- def longest_common_subpath(paths)
150
- return if paths.empty?
151
-
152
- lcsp = Pathname.new(paths[0])
153
-
154
- paths[1..-1].each do |path|
155
- until ascendant_of?(lcsp, path)
156
- if lcsp.root?
157
- # If we get here a root directory is not an ascendant of path.
158
- # This may happen if there are paths in different drives on
159
- # Windows.
160
- return
161
- else
162
- lcsp = lcsp.parent
163
- end
164
- end
165
- end
121
+ def restart?
122
+ @missing.any?(&:exist?)
123
+ end
166
124
 
167
- lcsp
125
+ def normalize_dirs!
126
+ @dirs.transform_keys! do |dir|
127
+ dir.exist? ? dir.realpath : dir
168
128
  end
129
+ end
169
130
 
170
- # Returns the deepest existing ascendant, which could be the argument itself.
171
- def existing_parent(dir)
172
- dir.ascend do |ascendant|
173
- break ascendant if ascendant.directory?
174
- end
131
+ def changed(modified, added, removed)
132
+ unless @updated.true?
133
+ @updated.make_true if (modified + added + removed).any? { |f| watching?(f) }
175
134
  end
135
+ end
176
136
 
177
- # Filters out directories which are descendants of others in the collection (stable).
178
- def filter_out_descendants(dirs)
179
- return dirs if dirs.length < 2
137
+ def watching?(file)
138
+ file = Pathname(file)
180
139
 
181
- dirs_sorted_by_nparts = dirs.sort_by { |dir| dir.each_filename.to_a.length }
182
- descendants = []
140
+ if @files.member?(file)
141
+ true
142
+ elsif file.directory?
143
+ false
144
+ else
145
+ ext = file.extname
183
146
 
184
- until dirs_sorted_by_nparts.empty?
185
- dir = dirs_sorted_by_nparts.shift
147
+ file.dirname.ascend do |dir|
148
+ matching = @dirs[dir]
186
149
 
187
- dirs_sorted_by_nparts.reject! do |possible_descendant|
188
- ascendant_of?(dir, possible_descendant) && descendants << possible_descendant
150
+ if matching && (matching.empty? || matching.include?(ext))
151
+ break true
152
+ elsif dir == @common_path || dir.root?
153
+ break false
189
154
  end
190
155
  end
191
-
192
- # Array#- preserves order.
193
- dirs - descendants
194
156
  end
157
+ end
195
158
 
196
- private
159
+ def directories_to_watch
160
+ dtw = @dirs.keys | @files.map(&:dirname)
161
+ accounted_for = dtw.to_set + Gem.path.map { |path| Pathname(path) }
162
+ dtw.reject { |dir| dir.ascend.drop(1).any? { |parent| accounted_for.include?(parent) } }
163
+ end
197
164
 
198
- def ascendant_of?(base, other)
199
- base != other && other.ascend do |ascendant|
200
- break true if base == ascendant
201
- end
202
- end
165
+ def common_path(paths)
166
+ paths.map { |path| path.ascend.to_a }.reduce(&:&)&.first
203
167
  end
168
+ end
204
169
  end
205
170
  end
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "active_support/callbacks"
4
+ require "concurrent/hash"
4
5
 
5
6
  module ActiveSupport
6
7
  class ExecutionWrapper
@@ -98,7 +98,6 @@ module ActiveSupport
98
98
  end
99
99
 
100
100
  private
101
-
102
101
  def watched
103
102
  @watched || begin
104
103
  all = @files.select { |f| File.exist?(f) }