activesupport 5.2.4.3 → 7.0.3

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 (228) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +244 -459
  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 +2 -2
  7. data/lib/active_support/backtrace_cleaner.rb +31 -5
  8. data/lib/active_support/benchmarkable.rb +3 -3
  9. data/lib/active_support/cache/file_store.rb +47 -41
  10. data/lib/active_support/cache/mem_cache_store.rb +151 -40
  11. data/lib/active_support/cache/memory_store.rb +68 -34
  12. data/lib/active_support/cache/null_store.rb +16 -3
  13. data/lib/active_support/cache/redis_cache_store.rb +103 -101
  14. data/lib/active_support/cache/strategy/local_cache.rb +56 -64
  15. data/lib/active_support/cache.rb +333 -116
  16. data/lib/active_support/callbacks.rb +244 -128
  17. data/lib/active_support/code_generator.rb +65 -0
  18. data/lib/active_support/concern.rb +72 -5
  19. data/lib/active_support/concurrency/load_interlock_aware_monitor.rb +16 -0
  20. data/lib/active_support/concurrency/share_lock.rb +2 -3
  21. data/lib/active_support/configurable.rb +15 -16
  22. data/lib/active_support/configuration_file.rb +51 -0
  23. data/lib/active_support/core_ext/array/access.rb +15 -7
  24. data/lib/active_support/core_ext/array/conversions.rb +18 -17
  25. data/lib/active_support/core_ext/array/deprecated_conversions.rb +25 -0
  26. data/lib/active_support/core_ext/array/extract.rb +21 -0
  27. data/lib/active_support/core_ext/array/grouping.rb +6 -6
  28. data/lib/active_support/core_ext/array/inquiry.rb +2 -2
  29. data/lib/active_support/core_ext/array.rb +2 -1
  30. data/lib/active_support/core_ext/benchmark.rb +2 -2
  31. data/lib/active_support/core_ext/big_decimal/conversions.rb +1 -1
  32. data/lib/active_support/core_ext/class/attribute.rb +32 -47
  33. data/lib/active_support/core_ext/class/subclasses.rb +9 -22
  34. data/lib/active_support/core_ext/date/blank.rb +1 -1
  35. data/lib/active_support/core_ext/date/calculations.rb +15 -14
  36. data/lib/active_support/core_ext/date/conversions.rb +16 -15
  37. data/lib/active_support/core_ext/date/deprecated_conversions.rb +26 -0
  38. data/lib/active_support/core_ext/date.rb +1 -0
  39. data/lib/active_support/core_ext/date_and_time/calculations.rb +41 -51
  40. data/lib/active_support/core_ext/date_and_time/compatibility.rb +15 -0
  41. data/lib/active_support/core_ext/date_and_time/zones.rb +0 -1
  42. data/lib/active_support/core_ext/date_time/blank.rb +1 -1
  43. data/lib/active_support/core_ext/date_time/calculations.rb +1 -1
  44. data/lib/active_support/core_ext/date_time/conversions.rb +13 -14
  45. data/lib/active_support/core_ext/date_time/deprecated_conversions.rb +22 -0
  46. data/lib/active_support/core_ext/date_time.rb +1 -0
  47. data/lib/active_support/core_ext/digest/uuid.rb +39 -13
  48. data/lib/active_support/core_ext/enumerable.rb +241 -76
  49. data/lib/active_support/core_ext/file/atomic.rb +3 -1
  50. data/lib/active_support/core_ext/hash/conversions.rb +3 -4
  51. data/lib/active_support/core_ext/hash/deep_transform_values.rb +46 -0
  52. data/lib/active_support/core_ext/hash/except.rb +2 -2
  53. data/lib/active_support/core_ext/hash/indifferent_access.rb +3 -3
  54. data/lib/active_support/core_ext/hash/keys.rb +2 -31
  55. data/lib/active_support/core_ext/hash/slice.rb +6 -27
  56. data/lib/active_support/core_ext/hash.rb +1 -2
  57. data/lib/active_support/core_ext/integer/multiple.rb +1 -1
  58. data/lib/active_support/core_ext/kernel/reporting.rb +4 -4
  59. data/lib/active_support/core_ext/kernel/singleton_class.rb +1 -1
  60. data/lib/active_support/core_ext/kernel.rb +0 -1
  61. data/lib/active_support/core_ext/load_error.rb +1 -1
  62. data/lib/active_support/core_ext/module/attr_internal.rb +2 -2
  63. data/lib/active_support/core_ext/module/attribute_accessors.rb +32 -39
  64. data/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb +35 -28
  65. data/lib/active_support/core_ext/module/concerning.rb +8 -2
  66. data/lib/active_support/core_ext/module/delegation.rb +70 -33
  67. data/lib/active_support/core_ext/module/introspection.rb +16 -15
  68. data/lib/active_support/core_ext/module/redefine_method.rb +8 -17
  69. data/lib/active_support/core_ext/module.rb +0 -1
  70. data/lib/active_support/core_ext/name_error.rb +23 -2
  71. data/lib/active_support/core_ext/numeric/conversions.rb +132 -129
  72. data/lib/active_support/core_ext/numeric/deprecated_conversions.rb +60 -0
  73. data/lib/active_support/core_ext/numeric.rb +1 -1
  74. data/lib/active_support/core_ext/object/acts_like.rb +29 -5
  75. data/lib/active_support/core_ext/object/blank.rb +3 -4
  76. data/lib/active_support/core_ext/object/deep_dup.rb +1 -1
  77. data/lib/active_support/core_ext/object/duplicable.rb +14 -110
  78. data/lib/active_support/core_ext/object/json.rb +44 -27
  79. data/lib/active_support/core_ext/object/to_query.rb +2 -2
  80. data/lib/active_support/core_ext/object/try.rb +24 -14
  81. data/lib/active_support/core_ext/object/with_options.rb +21 -2
  82. data/lib/active_support/core_ext/pathname/existence.rb +21 -0
  83. data/lib/active_support/core_ext/pathname.rb +3 -0
  84. data/lib/active_support/core_ext/range/compare_range.rb +23 -27
  85. data/lib/active_support/core_ext/range/conversions.rb +32 -30
  86. data/lib/active_support/core_ext/range/deprecated_conversions.rb +26 -0
  87. data/lib/active_support/core_ext/range/each.rb +1 -2
  88. data/lib/active_support/core_ext/range/include_time_with_zone.rb +4 -20
  89. data/lib/active_support/core_ext/range/overlaps.rb +1 -1
  90. data/lib/active_support/core_ext/range.rb +1 -1
  91. data/lib/active_support/core_ext/regexp.rb +8 -5
  92. data/lib/active_support/core_ext/securerandom.rb +23 -3
  93. data/lib/active_support/core_ext/string/access.rb +5 -16
  94. data/lib/active_support/core_ext/string/conversions.rb +3 -2
  95. data/lib/active_support/core_ext/string/filters.rb +42 -1
  96. data/lib/active_support/core_ext/string/inflections.rb +46 -7
  97. data/lib/active_support/core_ext/string/inquiry.rb +2 -1
  98. data/lib/active_support/core_ext/string/multibyte.rb +6 -5
  99. data/lib/active_support/core_ext/string/output_safety.rb +129 -20
  100. data/lib/active_support/core_ext/string/starts_ends_with.rb +2 -2
  101. data/lib/active_support/core_ext/string/strip.rb +3 -1
  102. data/lib/active_support/core_ext/symbol/starts_ends_with.rb +6 -0
  103. data/lib/active_support/core_ext/symbol.rb +3 -0
  104. data/lib/active_support/core_ext/time/calculations.rb +59 -10
  105. data/lib/active_support/core_ext/time/conversions.rb +15 -12
  106. data/lib/active_support/core_ext/time/deprecated_conversions.rb +22 -0
  107. data/lib/active_support/core_ext/time/zones.rb +7 -22
  108. data/lib/active_support/core_ext/time.rb +1 -0
  109. data/lib/active_support/core_ext/uri.rb +3 -22
  110. data/lib/active_support/core_ext.rb +2 -1
  111. data/lib/active_support/current_attributes/test_helper.rb +13 -0
  112. data/lib/active_support/current_attributes.rb +47 -16
  113. data/lib/active_support/dependencies/interlock.rb +10 -18
  114. data/lib/active_support/dependencies/require_dependency.rb +28 -0
  115. data/lib/active_support/dependencies.rb +60 -715
  116. data/lib/active_support/deprecation/behaviors.rb +21 -5
  117. data/lib/active_support/deprecation/disallowed.rb +56 -0
  118. data/lib/active_support/deprecation/instance_delegator.rb +0 -1
  119. data/lib/active_support/deprecation/method_wrappers.rb +18 -23
  120. data/lib/active_support/deprecation/proxy_wrappers.rb +31 -8
  121. data/lib/active_support/deprecation/reporting.rb +50 -7
  122. data/lib/active_support/deprecation.rb +7 -2
  123. data/lib/active_support/descendants_tracker.rb +190 -34
  124. data/lib/active_support/digest.rb +5 -3
  125. data/lib/active_support/duration/iso8601_parser.rb +5 -7
  126. data/lib/active_support/duration/iso8601_serializer.rb +27 -15
  127. data/lib/active_support/duration.rb +149 -67
  128. data/lib/active_support/encrypted_configuration.rb +12 -5
  129. data/lib/active_support/encrypted_file.rb +23 -5
  130. data/lib/active_support/environment_inquirer.rb +20 -0
  131. data/lib/active_support/error_reporter.rb +117 -0
  132. data/lib/active_support/evented_file_update_checker.rb +85 -122
  133. data/lib/active_support/execution_context/test_helper.rb +13 -0
  134. data/lib/active_support/execution_context.rb +53 -0
  135. data/lib/active_support/execution_wrapper.rb +44 -21
  136. data/lib/active_support/executor/test_helper.rb +7 -0
  137. data/lib/active_support/file_update_checker.rb +0 -1
  138. data/lib/active_support/fork_tracker.rb +71 -0
  139. data/lib/active_support/gem_version.rb +5 -5
  140. data/lib/active_support/hash_with_indifferent_access.rb +73 -43
  141. data/lib/active_support/html_safe_translation.rb +43 -0
  142. data/lib/active_support/i18n.rb +2 -0
  143. data/lib/active_support/i18n_railtie.rb +15 -8
  144. data/lib/active_support/inflector/inflections.rb +25 -14
  145. data/lib/active_support/inflector/methods.rb +38 -71
  146. data/lib/active_support/inflector/transliterate.rb +47 -18
  147. data/lib/active_support/isolated_execution_state.rb +72 -0
  148. data/lib/active_support/json/decoding.rb +25 -26
  149. data/lib/active_support/json/encoding.rb +14 -6
  150. data/lib/active_support/key_generator.rb +23 -38
  151. data/lib/active_support/lazy_load_hooks.rb +19 -5
  152. data/lib/active_support/locale/en.rb +33 -0
  153. data/lib/active_support/locale/en.yml +8 -4
  154. data/lib/active_support/log_subscriber/test_helper.rb +2 -2
  155. data/lib/active_support/log_subscriber.rb +51 -11
  156. data/lib/active_support/logger.rb +6 -22
  157. data/lib/active_support/logger_silence.rb +11 -19
  158. data/lib/active_support/logger_thread_safe_level.rb +45 -10
  159. data/lib/active_support/message_encryptor.rb +20 -19
  160. data/lib/active_support/message_verifier.rb +53 -21
  161. data/lib/active_support/messages/metadata.rb +13 -4
  162. data/lib/active_support/messages/rotation_configuration.rb +2 -1
  163. data/lib/active_support/messages/rotator.rb +10 -9
  164. data/lib/active_support/multibyte/chars.rb +17 -76
  165. data/lib/active_support/multibyte/unicode.rb +7 -331
  166. data/lib/active_support/multibyte.rb +1 -1
  167. data/lib/active_support/notifications/fanout.rb +163 -37
  168. data/lib/active_support/notifications/instrumenter.rb +90 -11
  169. data/lib/active_support/notifications.rb +88 -30
  170. data/lib/active_support/number_helper/number_converter.rb +6 -9
  171. data/lib/active_support/number_helper/number_to_currency_converter.rb +12 -12
  172. data/lib/active_support/number_helper/number_to_delimited_converter.rb +4 -3
  173. data/lib/active_support/number_helper/number_to_human_converter.rb +4 -3
  174. data/lib/active_support/number_helper/number_to_human_size_converter.rb +5 -4
  175. data/lib/active_support/number_helper/number_to_percentage_converter.rb +3 -1
  176. data/lib/active_support/number_helper/number_to_phone_converter.rb +3 -2
  177. data/lib/active_support/number_helper/number_to_rounded_converter.rb +12 -7
  178. data/lib/active_support/number_helper/rounding_helper.rb +12 -32
  179. data/lib/active_support/number_helper.rb +36 -12
  180. data/lib/active_support/option_merger.rb +15 -4
  181. data/lib/active_support/ordered_hash.rb +2 -2
  182. data/lib/active_support/ordered_options.rb +14 -4
  183. data/lib/active_support/parameter_filter.rb +138 -0
  184. data/lib/active_support/per_thread_registry.rb +6 -1
  185. data/lib/active_support/rails.rb +1 -10
  186. data/lib/active_support/railtie.rb +77 -5
  187. data/lib/active_support/reloader.rb +5 -6
  188. data/lib/active_support/rescuable.rb +8 -8
  189. data/lib/active_support/ruby_features.rb +7 -0
  190. data/lib/active_support/secure_compare_rotator.rb +51 -0
  191. data/lib/active_support/security_utils.rb +19 -12
  192. data/lib/active_support/string_inquirer.rb +2 -3
  193. data/lib/active_support/subscriber.rb +79 -46
  194. data/lib/active_support/tagged_logging.rb +58 -9
  195. data/lib/active_support/test_case.rb +79 -0
  196. data/lib/active_support/testing/assertions.rb +62 -11
  197. data/lib/active_support/testing/deprecation.rb +52 -2
  198. data/lib/active_support/testing/file_fixtures.rb +2 -0
  199. data/lib/active_support/testing/isolation.rb +4 -4
  200. data/lib/active_support/testing/method_call_assertions.rb +32 -5
  201. data/lib/active_support/testing/parallelization/server.rb +82 -0
  202. data/lib/active_support/testing/parallelization/worker.rb +103 -0
  203. data/lib/active_support/testing/parallelization.rb +55 -0
  204. data/lib/active_support/testing/parallelize_executor.rb +76 -0
  205. data/lib/active_support/testing/stream.rb +4 -7
  206. data/lib/active_support/testing/tagged_logging.rb +1 -1
  207. data/lib/active_support/testing/time_helpers.rb +60 -14
  208. data/lib/active_support/time_with_zone.rb +139 -64
  209. data/lib/active_support/values/time_zone.rb +66 -30
  210. data/lib/active_support/version.rb +1 -1
  211. data/lib/active_support/xml_mini/jdom.rb +3 -4
  212. data/lib/active_support/xml_mini/libxml.rb +7 -7
  213. data/lib/active_support/xml_mini/libxmlsax.rb +5 -5
  214. data/lib/active_support/xml_mini/nokogiri.rb +6 -6
  215. data/lib/active_support/xml_mini/nokogirisax.rb +4 -4
  216. data/lib/active_support/xml_mini/rexml.rb +11 -4
  217. data/lib/active_support/xml_mini.rb +7 -14
  218. data/lib/active_support.rb +30 -1
  219. metadata +64 -35
  220. data/lib/active_support/core_ext/array/prepend_and_append.rb +0 -9
  221. data/lib/active_support/core_ext/hash/compact.rb +0 -29
  222. data/lib/active_support/core_ext/hash/transform_values.rb +0 -32
  223. data/lib/active_support/core_ext/kernel/agnostics.rb +0 -13
  224. data/lib/active_support/core_ext/marshal.rb +0 -24
  225. data/lib/active_support/core_ext/module/reachable.rb +0 -11
  226. data/lib/active_support/core_ext/numeric/inquiry.rb +0 -28
  227. data/lib/active_support/core_ext/range/include_range.rb +0 -3
  228. data/lib/active_support/values/unicode_tables.dat +0 -0
@@ -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,14 +13,14 @@ 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
- SIGN_MARKER = /\A\-|\+|/
19
+ SIGN_MARKER = /\A-|\+|/
21
20
  DATE_MARKER = /P/
22
21
  TIME_MARKER = /T/
23
- DATE_COMPONENT = /(\-?\d+(?:[.,]\d+)?)(Y|M|D|W)/
24
- TIME_COMPONENT = /(\-?\d+(?:[.,]\d+)?)(H|M|S)/
22
+ DATE_COMPONENT = /(-?\d+(?:[.,]\d+)?)(Y|M|D|W)/
23
+ TIME_COMPONENT = /(-?\d+(?:[.,]\d+)?)(H|M|S)/
25
24
 
26
25
  DATE_TO_PART = { "Y" => :years, "M" => :months, "W" => :weeks, "D" => :days }
27
26
  TIME_TO_PART = { "H" => :hours, "M" => :minutes, "S" => :seconds }
@@ -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
- time << "#{sprintf(@precision ? "%0.0#{@precision}f" : '%g', parts[:seconds])}S"
30
+ time << "#{format_seconds(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,25 @@ 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
49
+ end
50
+
51
+ parts
52
+ end
53
+
54
+ def week_mixed_with_date?(parts)
55
+ parts.key?(:weeks) && (parts.keys & DATE_COMPONENTS).any?
56
+ end
57
+
58
+ def format_seconds(seconds)
59
+ if @precision
60
+ sprintf("%0.0#{@precision}f", seconds)
61
+ else
62
+ seconds.to_s
50
63
  end
51
- [parts, sign]
52
64
  end
53
65
  end
54
66
  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
@@ -12,7 +11,7 @@ module ActiveSupport
12
11
  #
13
12
  # 1.month.ago # equivalent to Time.now.advance(months: -1)
14
13
  class Duration
15
- class Scalar < Numeric #:nodoc:
14
+ class Scalar < Numeric # :nodoc:
16
15
  attr_reader :value
17
16
  delegate :to_i, :to_f, :to_s, to: :value
18
17
 
@@ -40,11 +39,11 @@ module ActiveSupport
40
39
 
41
40
  def +(other)
42
41
  if Duration === other
43
- seconds = value + other.parts[:seconds]
44
- new_parts = other.parts.merge(seconds: seconds)
42
+ seconds = value + other._parts.fetch(:seconds, 0)
43
+ new_parts = other._parts.merge(seconds: seconds)
45
44
  new_value = value + other.value
46
45
 
47
- Duration.new(new_value, new_parts)
46
+ Duration.new(new_value, new_parts, other.variable?)
48
47
  else
49
48
  calculate(:+, other)
50
49
  end
@@ -52,12 +51,12 @@ 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
 
60
- Duration.new(new_value, new_parts)
59
+ Duration.new(new_value, new_parts, other.variable?)
61
60
  else
62
61
  calculate(:-, other)
63
62
  end
@@ -65,10 +64,10 @@ 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
- Duration.new(new_value, new_parts)
70
+ Duration.new(new_value, new_parts, other.variable?)
72
71
  else
73
72
  calculate(:*, other)
74
73
  end
@@ -90,6 +89,10 @@ module ActiveSupport
90
89
  end
91
90
  end
92
91
 
92
+ def variable? # :nodoc:
93
+ false
94
+ end
95
+
93
96
  private
94
97
  def calculate(op, other)
95
98
  if Scalar === other
@@ -124,8 +127,9 @@ module ActiveSupport
124
127
  }.freeze
125
128
 
126
129
  PARTS = [:years, :months, :weeks, :days, :hours, :minutes, :seconds].freeze
130
+ VARIABLE_PARTS = [:years, :months, :weeks, :days].freeze
127
131
 
128
- attr_accessor :value, :parts
132
+ attr_reader :value
129
133
 
130
134
  autoload :ISO8601Parser, "active_support/duration/iso8601_parser"
131
135
  autoload :ISO8601Serializer, "active_support/duration/iso8601_serializer"
@@ -141,38 +145,38 @@ module ActiveSupport
141
145
  new(calculate_total_seconds(parts), parts)
142
146
  end
143
147
 
144
- def ===(other) #:nodoc:
148
+ def ===(other) # :nodoc:
145
149
  other.is_a?(Duration)
146
150
  rescue ::NoMethodError
147
151
  false
148
152
  end
149
153
 
150
- def seconds(value) #:nodoc:
151
- new(value, [[:seconds, value]])
154
+ def seconds(value) # :nodoc:
155
+ new(value, { seconds: value }, false)
152
156
  end
153
157
 
154
- def minutes(value) #:nodoc:
155
- new(value * SECONDS_PER_MINUTE, [[:minutes, value]])
158
+ def minutes(value) # :nodoc:
159
+ new(value * SECONDS_PER_MINUTE, { minutes: value }, false)
156
160
  end
157
161
 
158
- def hours(value) #:nodoc:
159
- new(value * SECONDS_PER_HOUR, [[:hours, value]])
162
+ def hours(value) # :nodoc:
163
+ new(value * SECONDS_PER_HOUR, { hours: value }, false)
160
164
  end
161
165
 
162
- def days(value) #:nodoc:
163
- new(value * SECONDS_PER_DAY, [[:days, value]])
166
+ def days(value) # :nodoc:
167
+ new(value * SECONDS_PER_DAY, { days: value }, true)
164
168
  end
165
169
 
166
- def weeks(value) #:nodoc:
167
- new(value * SECONDS_PER_WEEK, [[:weeks, value]])
170
+ def weeks(value) # :nodoc:
171
+ new(value * SECONDS_PER_WEEK, { weeks: value }, true)
168
172
  end
169
173
 
170
- def months(value) #:nodoc:
171
- new(value * SECONDS_PER_MONTH, [[:months, value]])
174
+ def months(value) # :nodoc:
175
+ new(value * SECONDS_PER_MONTH, { months: value }, true)
172
176
  end
173
177
 
174
- def years(value) #:nodoc:
175
- new(value * SECONDS_PER_YEAR, [[:years, value]])
178
+ def years(value) # :nodoc:
179
+ new(value * SECONDS_PER_YEAR, { years: value }, true)
176
180
  end
177
181
 
178
182
  # Creates a new Duration from a seconds value that is converted
@@ -182,24 +186,33 @@ module ActiveSupport
182
186
  # ActiveSupport::Duration.build(2716146).parts # => {:months=>1, :days=>1}
183
187
  #
184
188
  def build(value)
189
+ unless value.is_a?(::Numeric)
190
+ raise TypeError, "can't build an #{self.name} from a #{value.class.name}"
191
+ end
192
+
185
193
  parts = {}
186
- remainder = value.to_f
194
+ remainder_sign = value <=> 0
195
+ remainder = value.round(9).abs
196
+ variable = false
187
197
 
188
198
  PARTS.each do |part|
189
199
  unless part == :seconds
190
200
  part_in_seconds = PARTS_IN_SECONDS[part]
191
- parts[part] = remainder.div(part_in_seconds)
192
- remainder = (remainder % part_in_seconds).round(9)
201
+ parts[part] = remainder.div(part_in_seconds) * remainder_sign
202
+ remainder %= part_in_seconds
203
+
204
+ unless parts[part].zero?
205
+ variable ||= VARIABLE_PARTS.include?(part)
206
+ end
193
207
  end
194
- end
208
+ end unless value == 0
195
209
 
196
- parts[:seconds] = remainder
210
+ parts[:seconds] = remainder * remainder_sign
197
211
 
198
- new(value, parts)
212
+ new(value, parts, variable)
199
213
  end
200
214
 
201
215
  private
202
-
203
216
  def calculate_total_seconds(parts)
204
217
  parts.inject(0) do |total, (part, value)|
205
218
  total + value * PARTS_IN_SECONDS[part]
@@ -207,15 +220,28 @@ module ActiveSupport
207
220
  end
208
221
  end
209
222
 
210
- def initialize(value, parts) #:nodoc:
211
- @value, @parts = value, parts.to_h
212
- @parts.default = 0
213
- @parts.reject! { |k, v| v.zero? }
223
+ def initialize(value, parts, variable = nil) # :nodoc:
224
+ @value, @parts = value, parts
225
+ @parts.reject! { |k, v| v.zero? } unless value == 0
226
+ @parts.freeze
227
+ @variable = variable
228
+
229
+ if @variable.nil?
230
+ @variable = @parts.any? { |part, _| VARIABLE_PARTS.include?(part) }
231
+ end
232
+ end
233
+
234
+ # Returns a copy of the parts hash that defines the duration
235
+ def parts
236
+ @parts.dup
214
237
  end
215
238
 
216
- def coerce(other) #:nodoc:
217
- if Scalar === other
239
+ def coerce(other) # :nodoc:
240
+ case other
241
+ when Scalar
218
242
  [other, self]
243
+ when Duration
244
+ [Scalar.new(other.value), self]
219
245
  else
220
246
  [Scalar.new(other), self]
221
247
  end
@@ -235,14 +261,13 @@ module ActiveSupport
235
261
  # are treated as seconds.
236
262
  def +(other)
237
263
  if Duration === other
238
- parts = @parts.dup
239
- other.parts.each do |(key, value)|
240
- parts[key] += value
264
+ parts = @parts.merge(other._parts) do |_key, value, other_value|
265
+ value + other_value
241
266
  end
242
- Duration.new(value + other.value, parts)
267
+ Duration.new(value + other.value, parts, @variable || other.variable?)
243
268
  else
244
- seconds = @parts[:seconds] + other
245
- Duration.new(value + other, @parts.merge(seconds: seconds))
269
+ seconds = @parts.fetch(:seconds, 0) + other
270
+ Duration.new(value + other, @parts.merge(seconds: seconds), @variable)
246
271
  end
247
272
  end
248
273
 
@@ -255,9 +280,9 @@ module ActiveSupport
255
280
  # Multiplies this Duration by a Numeric and returns a new Duration.
256
281
  def *(other)
257
282
  if Scalar === other || Duration === other
258
- Duration.new(value * other.value, parts.map { |type, number| [type, number * other.value] })
283
+ Duration.new(value * other.value, @parts.transform_values { |number| number * other.value }, @variable || other.variable?)
259
284
  elsif Numeric === other
260
- Duration.new(value * other, parts.map { |type, number| [type, number * other] })
285
+ Duration.new(value * other, @parts.transform_values { |number| number * other }, @variable)
261
286
  else
262
287
  raise_type_error(other)
263
288
  end
@@ -266,11 +291,11 @@ module ActiveSupport
266
291
  # Divides this Duration by a Numeric and returns a new Duration.
267
292
  def /(other)
268
293
  if Scalar === other
269
- Duration.new(value / other.value, parts.map { |type, number| [type, number / other.value] })
294
+ Duration.new(value / other.value, @parts.transform_values { |number| number / other.value }, @variable)
270
295
  elsif Duration === other
271
296
  value / other.value
272
297
  elsif Numeric === other
273
- Duration.new(value / other, parts.map { |type, number| [type, number / other] })
298
+ Duration.new(value / other, @parts.transform_values { |number| number / other }, @variable)
274
299
  else
275
300
  raise_type_error(other)
276
301
  end
@@ -288,11 +313,15 @@ module ActiveSupport
288
313
  end
289
314
  end
290
315
 
291
- def -@ #:nodoc:
292
- Duration.new(-value, parts.map { |type, number| [type, -number] })
316
+ def -@ # :nodoc:
317
+ Duration.new(-value, @parts.transform_values(&:-@), @variable)
293
318
  end
294
319
 
295
- def is_a?(klass) #:nodoc:
320
+ def +@ # :nodoc:
321
+ self
322
+ end
323
+
324
+ def is_a?(klass) # :nodoc:
296
325
  Duration == klass || value.is_a?(klass)
297
326
  end
298
327
  alias :kind_of? :is_a?
@@ -336,12 +365,55 @@ module ActiveSupport
336
365
  # 1.year.to_i # => 31556952
337
366
  #
338
367
  # 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
368
+ # Date[https://ruby-doc.org/stdlib/libdoc/date/rdoc/Date.html] and
369
+ # Time[https://ruby-doc.org/stdlib/libdoc/time/rdoc/Time.html] should be used for precision
341
370
  # date and time arithmetic.
342
371
  def to_i
343
372
  @value.to_i
344
373
  end
374
+ alias :in_seconds :to_i
375
+
376
+ # Returns the amount of minutes a duration covers as a float
377
+ #
378
+ # 1.day.in_minutes # => 1440.0
379
+ def in_minutes
380
+ in_seconds / SECONDS_PER_MINUTE.to_f
381
+ end
382
+
383
+ # Returns the amount of hours a duration covers as a float
384
+ #
385
+ # 1.day.in_hours # => 24.0
386
+ def in_hours
387
+ in_seconds / SECONDS_PER_HOUR.to_f
388
+ end
389
+
390
+ # Returns the amount of days a duration covers as a float
391
+ #
392
+ # 12.hours.in_days # => 0.5
393
+ def in_days
394
+ in_seconds / SECONDS_PER_DAY.to_f
395
+ end
396
+
397
+ # Returns the amount of weeks a duration covers as a float
398
+ #
399
+ # 2.months.in_weeks # => 8.696
400
+ def in_weeks
401
+ in_seconds / SECONDS_PER_WEEK.to_f
402
+ end
403
+
404
+ # Returns the amount of months a duration covers as a float
405
+ #
406
+ # 9.weeks.in_months # => 2.07
407
+ def in_months
408
+ in_seconds / SECONDS_PER_MONTH.to_f
409
+ end
410
+
411
+ # Returns the amount of years a duration covers as a float
412
+ #
413
+ # 30.days.in_years # => 0.082
414
+ def in_years
415
+ in_seconds / SECONDS_PER_YEAR.to_f
416
+ end
345
417
 
346
418
  # Returns +true+ if +other+ is also a Duration instance, which has the
347
419
  # same parts as this one.
@@ -369,25 +441,24 @@ module ActiveSupport
369
441
  alias :until :ago
370
442
  alias :before :ago
371
443
 
372
- def inspect #:nodoc:
373
- return "0 seconds" if parts.empty?
444
+ def inspect # :nodoc:
445
+ return "#{value} seconds" if @parts.empty?
374
446
 
375
- parts.
376
- reduce(::Hash.new(0)) { |h, (l, r)| h[l] += r; h }.
447
+ @parts.
377
448
  sort_by { |unit, _ | PARTS.index(unit) }.
378
449
  map { |unit, val| "#{val} #{val == 1 ? unit.to_s.chop : unit.to_s}" }.
379
- to_sentence(locale: ::I18n.default_locale)
450
+ to_sentence(locale: false)
380
451
  end
381
452
 
382
- def as_json(options = nil) #:nodoc:
453
+ def as_json(options = nil) # :nodoc:
383
454
  to_i
384
455
  end
385
456
 
386
- def init_with(coder) #:nodoc:
457
+ def init_with(coder) # :nodoc:
387
458
  initialize(coder["value"], coder["parts"])
388
459
  end
389
460
 
390
- def encode_with(coder) #:nodoc:
461
+ def encode_with(coder) # :nodoc:
391
462
  coder.map = { "value" => @value, "parts" => @parts }
392
463
  end
393
464
 
@@ -397,11 +468,24 @@ module ActiveSupport
397
468
  ISO8601Serializer.new(self, precision: precision).serialize
398
469
  end
399
470
 
400
- private
471
+ def variable? # :nodoc:
472
+ @variable
473
+ end
474
+
475
+ def _parts # :nodoc:
476
+ @parts
477
+ end
401
478
 
479
+ private
402
480
  def sum(sign, time = ::Time.current)
403
- parts.inject(time) do |t, (type, number)|
404
- if t.acts_like?(:time) || t.acts_like?(:date)
481
+ unless time.acts_like?(:time) || time.acts_like?(:date)
482
+ raise ::ArgumentError, "expected a time or date, got #{time.inspect}"
483
+ end
484
+
485
+ if @parts.empty?
486
+ time.since(sign * value)
487
+ else
488
+ @parts.inject(time) do |t, (type, number)|
405
489
  if type == :seconds
406
490
  t.since(sign * number)
407
491
  elsif type == :minutes
@@ -411,8 +495,6 @@ module ActiveSupport
411
495
  else
412
496
  t.advance(type => sign * number)
413
497
  end
414
- else
415
- raise ::ArgumentError, "expected a time or date, got #{time.inspect}"
416
498
  end
417
499
  end
418
500
  end
@@ -34,16 +34,23 @@ module ActiveSupport
34
34
  end
35
35
 
36
36
  private
37
- def options
38
- @options ||= ActiveSupport::InheritableOptions.new(config)
37
+ def deep_transform(hash)
38
+ return hash unless hash.is_a?(Hash)
39
+
40
+ h = ActiveSupport::InheritableOptions.new
41
+ hash.each do |k, v|
42
+ h[k] = deep_transform(v)
43
+ end
44
+ h
39
45
  end
40
46
 
41
- def serialize(config)
42
- config.present? ? YAML.dump(config) : ""
47
+ def options
48
+ @options ||= ActiveSupport::InheritableOptions.new(deep_transform(config))
43
49
  end
44
50
 
45
51
  def deserialize(config)
46
- YAML.load(config).presence || {}
52
+ doc = YAML.respond_to?(:unsafe_load) ? YAML.unsafe_load(config) : YAML.load(config)
53
+ doc.presence || {}
47
54
  end
48
55
  end
49
56
  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
 
@@ -85,15 +98,20 @@ module ActiveSupport
85
98
 
86
99
 
87
100
  def read_env_key
88
- ENV[env_key]
101
+ ENV[env_key].presence
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