activesupport 5.1.1 → 6.1.1

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of activesupport might be problematic. Click here for more details.

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