activesupport 5.2.8.1 → 6.1.6.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 (188) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +426 -424
  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 +34 -34
  10. data/lib/active_support/cache/mem_cache_store.rb +39 -24
  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 +72 -45
  14. data/lib/active_support/cache/strategy/local_cache.rb +41 -26
  15. data/lib/active_support/cache.rb +148 -78
  16. data/lib/active_support/callbacks.rb +81 -64
  17. data/lib/active_support/concern.rb +70 -3
  18. data/lib/active_support/concurrency/share_lock.rb +0 -1
  19. data/lib/active_support/configurable.rb +10 -14
  20. data/lib/active_support/configuration_file.rb +51 -0
  21. data/lib/active_support/core_ext/array/access.rb +18 -6
  22. data/lib/active_support/core_ext/array/conversions.rb +5 -5
  23. data/lib/active_support/core_ext/array/extract.rb +21 -0
  24. data/lib/active_support/core_ext/array.rb +1 -1
  25. data/lib/active_support/core_ext/benchmark.rb +2 -2
  26. data/lib/active_support/core_ext/class/attribute.rb +32 -47
  27. data/lib/active_support/core_ext/class/subclasses.rb +17 -38
  28. data/lib/active_support/core_ext/date/calculations.rb +6 -5
  29. data/lib/active_support/core_ext/date/conversions.rb +2 -1
  30. data/lib/active_support/core_ext/date_and_time/calculations.rb +37 -47
  31. data/lib/active_support/core_ext/date_and_time/compatibility.rb +15 -0
  32. data/lib/active_support/core_ext/date_and_time/zones.rb +0 -1
  33. data/lib/active_support/core_ext/date_time/calculations.rb +1 -1
  34. data/lib/active_support/core_ext/date_time/conversions.rb +0 -1
  35. data/lib/active_support/core_ext/digest/uuid.rb +1 -0
  36. data/lib/active_support/core_ext/enumerable.rb +171 -75
  37. data/lib/active_support/core_ext/hash/conversions.rb +3 -3
  38. data/lib/active_support/core_ext/hash/deep_transform_values.rb +46 -0
  39. data/lib/active_support/core_ext/hash/except.rb +2 -2
  40. data/lib/active_support/core_ext/hash/keys.rb +1 -30
  41. data/lib/active_support/core_ext/hash/slice.rb +6 -27
  42. data/lib/active_support/core_ext/hash.rb +1 -2
  43. data/lib/active_support/core_ext/integer/multiple.rb +1 -1
  44. data/lib/active_support/core_ext/kernel.rb +0 -1
  45. data/lib/active_support/core_ext/load_error.rb +1 -1
  46. data/lib/active_support/core_ext/marshal.rb +2 -0
  47. data/lib/active_support/core_ext/module/attr_internal.rb +2 -2
  48. data/lib/active_support/core_ext/module/attribute_accessors.rb +30 -39
  49. data/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb +17 -19
  50. data/lib/active_support/core_ext/module/concerning.rb +8 -2
  51. data/lib/active_support/core_ext/module/delegation.rb +76 -33
  52. data/lib/active_support/core_ext/module/introspection.rb +16 -15
  53. data/lib/active_support/core_ext/module/redefine_method.rb +8 -17
  54. data/lib/active_support/core_ext/module.rb +0 -1
  55. data/lib/active_support/core_ext/name_error.rb +29 -2
  56. data/lib/active_support/core_ext/numeric/conversions.rb +129 -129
  57. data/lib/active_support/core_ext/numeric.rb +0 -1
  58. data/lib/active_support/core_ext/object/blank.rb +1 -2
  59. data/lib/active_support/core_ext/object/deep_dup.rb +1 -1
  60. data/lib/active_support/core_ext/object/duplicable.rb +7 -114
  61. data/lib/active_support/core_ext/object/json.rb +14 -2
  62. data/lib/active_support/core_ext/object/try.rb +17 -7
  63. data/lib/active_support/core_ext/object/with_options.rb +1 -1
  64. data/lib/active_support/core_ext/range/compare_range.rb +34 -13
  65. data/lib/active_support/core_ext/range/conversions.rb +31 -29
  66. data/lib/active_support/core_ext/range/each.rb +0 -1
  67. data/lib/active_support/core_ext/range/include_time_with_zone.rb +8 -3
  68. data/lib/active_support/core_ext/regexp.rb +8 -5
  69. data/lib/active_support/core_ext/securerandom.rb +23 -3
  70. data/lib/active_support/core_ext/string/access.rb +5 -16
  71. data/lib/active_support/core_ext/string/conversions.rb +1 -0
  72. data/lib/active_support/core_ext/string/filters.rb +42 -1
  73. data/lib/active_support/core_ext/string/inflections.rb +45 -6
  74. data/lib/active_support/core_ext/string/inquiry.rb +1 -0
  75. data/lib/active_support/core_ext/string/multibyte.rb +6 -5
  76. data/lib/active_support/core_ext/string/output_safety.rb +70 -13
  77. data/lib/active_support/core_ext/string/starts_ends_with.rb +2 -2
  78. data/lib/active_support/core_ext/string/strip.rb +3 -1
  79. data/lib/active_support/core_ext/symbol/starts_ends_with.rb +14 -0
  80. data/lib/active_support/core_ext/symbol.rb +3 -0
  81. data/lib/active_support/core_ext/time/calculations.rb +53 -3
  82. data/lib/active_support/core_ext/time/conversions.rb +2 -0
  83. data/lib/active_support/core_ext/uri.rb +6 -1
  84. data/lib/active_support/core_ext.rb +1 -1
  85. data/lib/active_support/current_attributes/test_helper.rb +13 -0
  86. data/lib/active_support/current_attributes.rb +16 -2
  87. data/lib/active_support/dependencies/zeitwerk_integration.rb +120 -0
  88. data/lib/active_support/dependencies.rb +109 -34
  89. data/lib/active_support/deprecation/behaviors.rb +16 -3
  90. data/lib/active_support/deprecation/disallowed.rb +56 -0
  91. data/lib/active_support/deprecation/instance_delegator.rb +0 -1
  92. data/lib/active_support/deprecation/method_wrappers.rb +18 -23
  93. data/lib/active_support/deprecation/proxy_wrappers.rb +29 -6
  94. data/lib/active_support/deprecation/reporting.rb +50 -7
  95. data/lib/active_support/deprecation.rb +6 -1
  96. data/lib/active_support/descendants_tracker.rb +59 -9
  97. data/lib/active_support/digest.rb +2 -0
  98. data/lib/active_support/duration/iso8601_parser.rb +2 -4
  99. data/lib/active_support/duration/iso8601_serializer.rb +18 -14
  100. data/lib/active_support/duration.rb +82 -33
  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 +2 -1
  106. data/lib/active_support/file_update_checker.rb +0 -1
  107. data/lib/active_support/fork_tracker.rb +64 -0
  108. data/lib/active_support/gem_version.rb +3 -3
  109. data/lib/active_support/hash_with_indifferent_access.rb +70 -42
  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/fanout.rb +116 -16
  133. data/lib/active_support/notifications/instrumenter.rb +71 -9
  134. data/lib/active_support/notifications.rb +72 -8
  135. data/lib/active_support/number_helper/number_converter.rb +5 -6
  136. data/lib/active_support/number_helper/number_to_currency_converter.rb +4 -9
  137. data/lib/active_support/number_helper/number_to_delimited_converter.rb +3 -2
  138. data/lib/active_support/number_helper/number_to_human_converter.rb +4 -3
  139. data/lib/active_support/number_helper/number_to_human_size_converter.rb +4 -3
  140. data/lib/active_support/number_helper/number_to_percentage_converter.rb +3 -1
  141. data/lib/active_support/number_helper/number_to_phone_converter.rb +2 -1
  142. data/lib/active_support/number_helper/number_to_rounded_converter.rb +12 -7
  143. data/lib/active_support/number_helper/rounding_helper.rb +12 -28
  144. data/lib/active_support/number_helper.rb +38 -12
  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 +2 -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/server.rb +78 -0
  166. data/lib/active_support/testing/parallelization/worker.rb +100 -0
  167. data/lib/active_support/testing/parallelization.rb +51 -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 +34 -17
  172. data/lib/active_support/xml_mini/jdom.rb +2 -3
  173. data/lib/active_support/xml_mini/libxml.rb +2 -2
  174. data/lib/active_support/xml_mini/libxmlsax.rb +4 -4
  175. data/lib/active_support/xml_mini/nokogiri.rb +2 -2
  176. data/lib/active_support/xml_mini/nokogirisax.rb +3 -3
  177. data/lib/active_support/xml_mini/rexml.rb +10 -3
  178. data/lib/active_support/xml_mini.rb +2 -10
  179. data/lib/active_support.rb +14 -1
  180. metadata +54 -27
  181. data/lib/active_support/core_ext/array/prepend_and_append.rb +0 -9
  182. data/lib/active_support/core_ext/hash/compact.rb +0 -29
  183. data/lib/active_support/core_ext/hash/transform_values.rb +0 -32
  184. data/lib/active_support/core_ext/kernel/agnostics.rb +0 -13
  185. data/lib/active_support/core_ext/module/reachable.rb +0 -11
  186. data/lib/active_support/core_ext/numeric/inquiry.rb +0 -28
  187. data/lib/active_support/core_ext/range/include_range.rb +0 -3
  188. 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,24 +181,28 @@ 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.round(9)
189
+ remainder_sign = value <=> 0
190
+ remainder = value.round(9).abs
187
191
 
188
192
  PARTS.each do |part|
189
193
  unless part == :seconds
190
194
  part_in_seconds = PARTS_IN_SECONDS[part]
191
- parts[part] = remainder.div(part_in_seconds)
195
+ parts[part] = remainder.div(part_in_seconds) * remainder_sign
192
196
  remainder %= part_in_seconds
193
197
  end
194
198
  end unless value == 0
195
199
 
196
- parts[:seconds] = remainder
200
+ parts[:seconds] = remainder * remainder_sign
197
201
 
198
202
  new(value, parts)
199
203
  end
200
204
 
201
205
  private
202
-
203
206
  def calculate_total_seconds(parts)
204
207
  parts.inject(0) do |total, (part, value)|
205
208
  total + value * PARTS_IN_SECONDS[part]
@@ -208,14 +211,16 @@ module ActiveSupport
208
211
  end
209
212
 
210
213
  def initialize(value, parts) #:nodoc:
211
- @value, @parts = value, parts.to_h
212
- @parts.default = 0
214
+ @value, @parts = value, parts
213
215
  @parts.reject! { |k, v| v.zero? } unless value == 0
214
216
  end
215
217
 
216
218
  def coerce(other) #:nodoc:
217
- if Scalar === other
219
+ case other
220
+ when Scalar
218
221
  [other, self]
222
+ when Duration
223
+ [Scalar.new(other.value), self]
219
224
  else
220
225
  [Scalar.new(other), self]
221
226
  end
@@ -235,13 +240,12 @@ module ActiveSupport
235
240
  # are treated as seconds.
236
241
  def +(other)
237
242
  if Duration === other
238
- parts = @parts.dup
239
- other.parts.each do |(key, value)|
240
- parts[key] += value
243
+ parts = @parts.merge(other.parts) do |_key, value, other_value|
244
+ value + other_value
241
245
  end
242
246
  Duration.new(value + other.value, parts)
243
247
  else
244
- seconds = @parts[:seconds] + other
248
+ seconds = @parts.fetch(:seconds, 0) + other
245
249
  Duration.new(value + other, @parts.merge(seconds: seconds))
246
250
  end
247
251
  end
@@ -255,9 +259,9 @@ module ActiveSupport
255
259
  # Multiplies this Duration by a Numeric and returns a new Duration.
256
260
  def *(other)
257
261
  if Scalar === other || Duration === other
258
- Duration.new(value * other.value, parts.map { |type, number| [type, number * other.value] })
262
+ Duration.new(value * other.value, parts.transform_values { |number| number * other.value })
259
263
  elsif Numeric === other
260
- Duration.new(value * other, parts.map { |type, number| [type, number * other] })
264
+ Duration.new(value * other, parts.transform_values { |number| number * other })
261
265
  else
262
266
  raise_type_error(other)
263
267
  end
@@ -266,11 +270,11 @@ module ActiveSupport
266
270
  # Divides this Duration by a Numeric and returns a new Duration.
267
271
  def /(other)
268
272
  if Scalar === other
269
- Duration.new(value / other.value, parts.map { |type, number| [type, number / other.value] })
273
+ Duration.new(value / other.value, parts.transform_values { |number| number / other.value })
270
274
  elsif Duration === other
271
275
  value / other.value
272
276
  elsif Numeric === other
273
- Duration.new(value / other, parts.map { |type, number| [type, number / other] })
277
+ Duration.new(value / other, parts.transform_values { |number| number / other })
274
278
  else
275
279
  raise_type_error(other)
276
280
  end
@@ -289,7 +293,11 @@ module ActiveSupport
289
293
  end
290
294
 
291
295
  def -@ #:nodoc:
292
- Duration.new(-value, parts.map { |type, number| [type, -number] })
296
+ Duration.new(-value, parts.transform_values(&:-@))
297
+ end
298
+
299
+ def +@ #:nodoc:
300
+ self
293
301
  end
294
302
 
295
303
  def is_a?(klass) #:nodoc:
@@ -336,12 +344,55 @@ module ActiveSupport
336
344
  # 1.year.to_i # => 31556952
337
345
  #
338
346
  # 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
347
+ # Date[https://ruby-doc.org/stdlib/libdoc/date/rdoc/Date.html] and
348
+ # Time[https://ruby-doc.org/stdlib/libdoc/time/rdoc/Time.html] should be used for precision
341
349
  # date and time arithmetic.
342
350
  def to_i
343
351
  @value.to_i
344
352
  end
353
+ alias :in_seconds :to_i
354
+
355
+ # Returns the amount of minutes a duration covers as a float
356
+ #
357
+ # 1.day.in_minutes # => 1440.0
358
+ def in_minutes
359
+ in_seconds / SECONDS_PER_MINUTE.to_f
360
+ end
361
+
362
+ # Returns the amount of hours a duration covers as a float
363
+ #
364
+ # 1.day.in_hours # => 24.0
365
+ def in_hours
366
+ in_seconds / SECONDS_PER_HOUR.to_f
367
+ end
368
+
369
+ # Returns the amount of days a duration covers as a float
370
+ #
371
+ # 12.hours.in_days # => 0.5
372
+ def in_days
373
+ in_seconds / SECONDS_PER_DAY.to_f
374
+ end
375
+
376
+ # Returns the amount of weeks a duration covers as a float
377
+ #
378
+ # 2.months.in_weeks # => 8.696
379
+ def in_weeks
380
+ in_seconds / SECONDS_PER_WEEK.to_f
381
+ end
382
+
383
+ # Returns the amount of months a duration covers as a float
384
+ #
385
+ # 9.weeks.in_months # => 2.07
386
+ def in_months
387
+ in_seconds / SECONDS_PER_MONTH.to_f
388
+ end
389
+
390
+ # Returns the amount of years a duration covers as a float
391
+ #
392
+ # 30.days.in_years # => 0.082
393
+ def in_years
394
+ in_seconds / SECONDS_PER_YEAR.to_f
395
+ end
345
396
 
346
397
  # Returns +true+ if +other+ is also a Duration instance, which has the
347
398
  # same parts as this one.
@@ -370,10 +421,9 @@ module ActiveSupport
370
421
  alias :before :ago
371
422
 
372
423
  def inspect #:nodoc:
373
- return "0 seconds" if parts.empty?
424
+ return "#{value} seconds" if parts.empty?
374
425
 
375
426
  parts.
376
- reduce(::Hash.new(0)) { |h, (l, r)| h[l] += r; h }.
377
427
  sort_by { |unit, _ | PARTS.index(unit) }.
378
428
  map { |unit, val| "#{val} #{val == 1 ? unit.to_s.chop : unit.to_s}" }.
379
429
  to_sentence(locale: ::I18n.default_locale)
@@ -398,7 +448,6 @@ module ActiveSupport
398
448
  end
399
449
 
400
450
  private
401
-
402
451
  def sum(sign, time = ::Time.current)
403
452
  unless time.acts_like?(:time) || time.acts_like?(:date)
404
453
  raise ::ArgumentError, "expected a time or date, got #{time.inspect}"
@@ -38,10 +38,6 @@ 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
42
  YAML.load(config).presence || {}
47
43
  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
 
@@ -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
@@ -65,7 +66,7 @@ module ActiveSupport
65
66
  def self.run!(reset: false)
66
67
  if reset
67
68
  lost_instance = active.delete(Thread.current)
68
- lost_instance.complete! unless lost_instance.nil?
69
+ lost_instance&.complete!
69
70
  else
70
71
  return Null if active?
71
72
  end
@@ -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) }