activesupport 6.1.0 → 7.1.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (225) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1075 -325
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +7 -7
  5. data/lib/active_support/actionable_error.rb +4 -2
  6. data/lib/active_support/array_inquirer.rb +2 -2
  7. data/lib/active_support/backtrace_cleaner.rb +32 -7
  8. data/lib/active_support/benchmarkable.rb +3 -2
  9. data/lib/active_support/broadcast_logger.rb +251 -0
  10. data/lib/active_support/builder.rb +1 -1
  11. data/lib/active_support/cache/coder.rb +153 -0
  12. data/lib/active_support/cache/entry.rb +134 -0
  13. data/lib/active_support/cache/file_store.rb +53 -20
  14. data/lib/active_support/cache/mem_cache_store.rb +201 -62
  15. data/lib/active_support/cache/memory_store.rb +86 -24
  16. data/lib/active_support/cache/null_store.rb +16 -2
  17. data/lib/active_support/cache/redis_cache_store.rb +186 -193
  18. data/lib/active_support/cache/serializer_with_fallback.rb +175 -0
  19. data/lib/active_support/cache/strategy/local_cache.rb +63 -71
  20. data/lib/active_support/cache.rb +487 -249
  21. data/lib/active_support/callbacks.rb +227 -105
  22. data/lib/active_support/code_generator.rb +70 -0
  23. data/lib/active_support/concern.rb +9 -7
  24. data/lib/active_support/concurrency/load_interlock_aware_monitor.rb +44 -7
  25. data/lib/active_support/concurrency/null_lock.rb +13 -0
  26. data/lib/active_support/concurrency/share_lock.rb +2 -2
  27. data/lib/active_support/configurable.rb +18 -5
  28. data/lib/active_support/configuration_file.rb +7 -2
  29. data/lib/active_support/core_ext/array/access.rb +1 -5
  30. data/lib/active_support/core_ext/array/conversions.rb +15 -13
  31. data/lib/active_support/core_ext/array/grouping.rb +6 -6
  32. data/lib/active_support/core_ext/array/inquiry.rb +2 -2
  33. data/lib/active_support/core_ext/big_decimal/conversions.rb +1 -1
  34. data/lib/active_support/core_ext/class/subclasses.rb +37 -26
  35. data/lib/active_support/core_ext/date/blank.rb +1 -1
  36. data/lib/active_support/core_ext/date/calculations.rb +24 -9
  37. data/lib/active_support/core_ext/date/conversions.rb +16 -15
  38. data/lib/active_support/core_ext/date_and_time/calculations.rb +14 -4
  39. data/lib/active_support/core_ext/date_and_time/compatibility.rb +1 -1
  40. data/lib/active_support/core_ext/date_time/blank.rb +1 -1
  41. data/lib/active_support/core_ext/date_time/calculations.rb +4 -0
  42. data/lib/active_support/core_ext/date_time/conversions.rb +19 -15
  43. data/lib/active_support/core_ext/digest/uuid.rb +30 -13
  44. data/lib/active_support/core_ext/enumerable.rb +85 -83
  45. data/lib/active_support/core_ext/erb/util.rb +196 -0
  46. data/lib/active_support/core_ext/file/atomic.rb +3 -1
  47. data/lib/active_support/core_ext/hash/conversions.rb +1 -2
  48. data/lib/active_support/core_ext/hash/deep_merge.rb +22 -14
  49. data/lib/active_support/core_ext/hash/deep_transform_values.rb +3 -3
  50. data/lib/active_support/core_ext/hash/indifferent_access.rb +3 -3
  51. data/lib/active_support/core_ext/hash/keys.rb +4 -4
  52. data/lib/active_support/core_ext/integer/inflections.rb +12 -12
  53. data/lib/active_support/core_ext/kernel/reporting.rb +4 -4
  54. data/lib/active_support/core_ext/kernel/singleton_class.rb +1 -1
  55. data/lib/active_support/core_ext/module/attribute_accessors.rb +8 -0
  56. data/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb +49 -22
  57. data/lib/active_support/core_ext/module/concerning.rb +6 -6
  58. data/lib/active_support/core_ext/module/delegation.rb +81 -43
  59. data/lib/active_support/core_ext/module/deprecation.rb +15 -12
  60. data/lib/active_support/core_ext/module/introspection.rb +0 -1
  61. data/lib/active_support/core_ext/name_error.rb +2 -8
  62. data/lib/active_support/core_ext/numeric/bytes.rb +9 -0
  63. data/lib/active_support/core_ext/numeric/conversions.rb +82 -77
  64. data/lib/active_support/core_ext/object/acts_like.rb +29 -5
  65. data/lib/active_support/core_ext/object/blank.rb +2 -2
  66. data/lib/active_support/core_ext/object/deep_dup.rb +17 -1
  67. data/lib/active_support/core_ext/object/duplicable.rb +31 -11
  68. data/lib/active_support/core_ext/object/inclusion.rb +13 -5
  69. data/lib/active_support/core_ext/object/instance_variables.rb +22 -12
  70. data/lib/active_support/core_ext/object/json.rb +49 -27
  71. data/lib/active_support/core_ext/object/to_query.rb +2 -4
  72. data/lib/active_support/core_ext/object/try.rb +20 -20
  73. data/lib/active_support/core_ext/object/with.rb +44 -0
  74. data/lib/active_support/core_ext/object/with_options.rb +25 -6
  75. data/lib/active_support/core_ext/object.rb +1 -0
  76. data/lib/active_support/core_ext/pathname/blank.rb +16 -0
  77. data/lib/active_support/core_ext/pathname/existence.rb +23 -0
  78. data/lib/active_support/core_ext/pathname.rb +4 -0
  79. data/lib/active_support/core_ext/range/compare_range.rb +0 -25
  80. data/lib/active_support/core_ext/range/conversions.rb +34 -13
  81. data/lib/active_support/core_ext/range/each.rb +1 -1
  82. data/lib/active_support/core_ext/range/overlap.rb +40 -0
  83. data/lib/active_support/core_ext/range.rb +1 -2
  84. data/lib/active_support/core_ext/securerandom.rb +25 -13
  85. data/lib/active_support/core_ext/string/conversions.rb +2 -2
  86. data/lib/active_support/core_ext/string/filters.rb +21 -15
  87. data/lib/active_support/core_ext/string/indent.rb +1 -1
  88. data/lib/active_support/core_ext/string/inflections.rb +17 -10
  89. data/lib/active_support/core_ext/string/inquiry.rb +1 -1
  90. data/lib/active_support/core_ext/string/output_safety.rb +85 -165
  91. data/lib/active_support/core_ext/symbol/starts_ends_with.rb +0 -8
  92. data/lib/active_support/core_ext/thread/backtrace/location.rb +12 -0
  93. data/lib/active_support/core_ext/time/calculations.rb +30 -8
  94. data/lib/active_support/core_ext/time/conversions.rb +15 -13
  95. data/lib/active_support/core_ext/time/zones.rb +12 -28
  96. data/lib/active_support/core_ext.rb +2 -1
  97. data/lib/active_support/current_attributes.rb +47 -20
  98. data/lib/active_support/deep_mergeable.rb +53 -0
  99. data/lib/active_support/dependencies/autoload.rb +17 -12
  100. data/lib/active_support/dependencies/interlock.rb +10 -18
  101. data/lib/active_support/dependencies/require_dependency.rb +28 -0
  102. data/lib/active_support/dependencies.rb +58 -788
  103. data/lib/active_support/deprecation/behaviors.rb +66 -40
  104. data/lib/active_support/deprecation/constant_accessor.rb +5 -4
  105. data/lib/active_support/deprecation/deprecators.rb +104 -0
  106. data/lib/active_support/deprecation/disallowed.rb +6 -8
  107. data/lib/active_support/deprecation/instance_delegator.rb +31 -4
  108. data/lib/active_support/deprecation/method_wrappers.rb +9 -26
  109. data/lib/active_support/deprecation/proxy_wrappers.rb +38 -23
  110. data/lib/active_support/deprecation/reporting.rb +43 -26
  111. data/lib/active_support/deprecation.rb +32 -5
  112. data/lib/active_support/deprecator.rb +7 -0
  113. data/lib/active_support/descendants_tracker.rb +150 -72
  114. data/lib/active_support/digest.rb +5 -3
  115. data/lib/active_support/duration/iso8601_parser.rb +3 -3
  116. data/lib/active_support/duration/iso8601_serializer.rb +9 -3
  117. data/lib/active_support/duration.rb +83 -52
  118. data/lib/active_support/encrypted_configuration.rb +72 -9
  119. data/lib/active_support/encrypted_file.rb +29 -13
  120. data/lib/active_support/environment_inquirer.rb +23 -3
  121. data/lib/active_support/error_reporter/test_helper.rb +15 -0
  122. data/lib/active_support/error_reporter.rb +203 -0
  123. data/lib/active_support/evented_file_update_checker.rb +20 -7
  124. data/lib/active_support/execution_context/test_helper.rb +13 -0
  125. data/lib/active_support/execution_context.rb +53 -0
  126. data/lib/active_support/execution_wrapper.rb +44 -22
  127. data/lib/active_support/executor/test_helper.rb +7 -0
  128. data/lib/active_support/file_update_checker.rb +4 -2
  129. data/lib/active_support/fork_tracker.rb +28 -11
  130. data/lib/active_support/gem_version.rb +4 -4
  131. data/lib/active_support/gzip.rb +2 -0
  132. data/lib/active_support/hash_with_indifferent_access.rb +44 -19
  133. data/lib/active_support/html_safe_translation.rb +53 -0
  134. data/lib/active_support/i18n.rb +2 -1
  135. data/lib/active_support/i18n_railtie.rb +21 -14
  136. data/lib/active_support/inflector/inflections.rb +25 -7
  137. data/lib/active_support/inflector/methods.rb +50 -64
  138. data/lib/active_support/inflector/transliterate.rb +4 -2
  139. data/lib/active_support/isolated_execution_state.rb +76 -0
  140. data/lib/active_support/json/decoding.rb +2 -1
  141. data/lib/active_support/json/encoding.rb +27 -45
  142. data/lib/active_support/key_generator.rb +31 -6
  143. data/lib/active_support/lazy_load_hooks.rb +33 -7
  144. data/lib/active_support/locale/en.yml +4 -2
  145. data/lib/active_support/log_subscriber/test_helper.rb +2 -2
  146. data/lib/active_support/log_subscriber.rb +97 -35
  147. data/lib/active_support/logger.rb +9 -60
  148. data/lib/active_support/logger_thread_safe_level.rb +11 -34
  149. data/lib/active_support/message_encryptor.rb +206 -56
  150. data/lib/active_support/message_encryptors.rb +141 -0
  151. data/lib/active_support/message_pack/cache_serializer.rb +23 -0
  152. data/lib/active_support/message_pack/extensions.rb +292 -0
  153. data/lib/active_support/message_pack/serializer.rb +63 -0
  154. data/lib/active_support/message_pack.rb +50 -0
  155. data/lib/active_support/message_verifier.rb +235 -84
  156. data/lib/active_support/message_verifiers.rb +135 -0
  157. data/lib/active_support/messages/codec.rb +65 -0
  158. data/lib/active_support/messages/metadata.rb +112 -46
  159. data/lib/active_support/messages/rotation_coordinator.rb +93 -0
  160. data/lib/active_support/messages/rotator.rb +34 -32
  161. data/lib/active_support/messages/serializer_with_fallback.rb +158 -0
  162. data/lib/active_support/multibyte/chars.rb +12 -11
  163. data/lib/active_support/multibyte/unicode.rb +9 -49
  164. data/lib/active_support/multibyte.rb +1 -1
  165. data/lib/active_support/notifications/fanout.rb +304 -114
  166. data/lib/active_support/notifications/instrumenter.rb +117 -35
  167. data/lib/active_support/notifications.rb +25 -25
  168. data/lib/active_support/number_helper/number_converter.rb +14 -7
  169. data/lib/active_support/number_helper/number_to_currency_converter.rb +11 -6
  170. data/lib/active_support/number_helper/number_to_delimited_converter.rb +1 -1
  171. data/lib/active_support/number_helper/number_to_human_size_converter.rb +4 -4
  172. data/lib/active_support/number_helper/number_to_phone_converter.rb +2 -1
  173. data/lib/active_support/number_helper/number_to_rounded_converter.rb +10 -6
  174. data/lib/active_support/number_helper/rounding_helper.rb +2 -6
  175. data/lib/active_support/number_helper.rb +379 -319
  176. data/lib/active_support/option_merger.rb +10 -18
  177. data/lib/active_support/ordered_hash.rb +4 -4
  178. data/lib/active_support/ordered_options.rb +15 -1
  179. data/lib/active_support/parameter_filter.rb +105 -81
  180. data/lib/active_support/proxy_object.rb +2 -0
  181. data/lib/active_support/railtie.rb +83 -21
  182. data/lib/active_support/reloader.rb +13 -5
  183. data/lib/active_support/rescuable.rb +18 -16
  184. data/lib/active_support/ruby_features.rb +7 -0
  185. data/lib/active_support/secure_compare_rotator.rb +18 -11
  186. data/lib/active_support/security_utils.rb +1 -1
  187. data/lib/active_support/string_inquirer.rb +3 -3
  188. data/lib/active_support/subscriber.rb +11 -40
  189. data/lib/active_support/syntax_error_proxy.rb +60 -0
  190. data/lib/active_support/tagged_logging.rb +65 -25
  191. data/lib/active_support/test_case.rb +166 -27
  192. data/lib/active_support/testing/assertions.rb +61 -15
  193. data/lib/active_support/testing/autorun.rb +0 -2
  194. data/lib/active_support/testing/constant_stubbing.rb +32 -0
  195. data/lib/active_support/testing/deprecation.rb +53 -2
  196. data/lib/active_support/testing/error_reporter_assertions.rb +107 -0
  197. data/lib/active_support/testing/isolation.rb +30 -29
  198. data/lib/active_support/testing/method_call_assertions.rb +24 -11
  199. data/lib/active_support/testing/parallelization/server.rb +4 -0
  200. data/lib/active_support/testing/parallelization/worker.rb +3 -0
  201. data/lib/active_support/testing/parallelization.rb +4 -0
  202. data/lib/active_support/testing/parallelize_executor.rb +81 -0
  203. data/lib/active_support/testing/setup_and_teardown.rb +2 -0
  204. data/lib/active_support/testing/stream.rb +4 -6
  205. data/lib/active_support/testing/strict_warnings.rb +39 -0
  206. data/lib/active_support/testing/tagged_logging.rb +1 -1
  207. data/lib/active_support/testing/time_helpers.rb +49 -16
  208. data/lib/active_support/time_with_zone.rb +39 -28
  209. data/lib/active_support/values/time_zone.rb +50 -18
  210. data/lib/active_support/version.rb +1 -1
  211. data/lib/active_support/xml_mini/jdom.rb +4 -11
  212. data/lib/active_support/xml_mini/libxml.rb +5 -5
  213. data/lib/active_support/xml_mini/libxmlsax.rb +1 -1
  214. data/lib/active_support/xml_mini/nokogiri.rb +5 -5
  215. data/lib/active_support/xml_mini/nokogirisax.rb +2 -2
  216. data/lib/active_support/xml_mini/rexml.rb +2 -2
  217. data/lib/active_support/xml_mini.rb +7 -6
  218. data/lib/active_support.rb +28 -1
  219. metadata +150 -18
  220. data/lib/active_support/core_ext/marshal.rb +0 -26
  221. data/lib/active_support/core_ext/range/include_time_with_zone.rb +0 -28
  222. data/lib/active_support/core_ext/range/overlaps.rb +0 -10
  223. data/lib/active_support/core_ext/uri.rb +0 -29
  224. data/lib/active_support/dependencies/zeitwerk_integration.rb +0 -117
  225. data/lib/active_support/per_thread_registry.rb +0 -60
@@ -3,15 +3,16 @@
3
3
  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
- require "active_support/core_ext/string/filters"
7
6
 
8
7
  module ActiveSupport
8
+ # = Active Support \Duration
9
+ #
9
10
  # Provides accurate date and time measurements using Date#advance and
10
11
  # Time#advance, respectively. It mainly supports the methods on Numeric.
11
12
  #
12
13
  # 1.month.ago # equivalent to Time.now.advance(months: -1)
13
14
  class Duration
14
- class Scalar < Numeric #:nodoc:
15
+ class Scalar < Numeric # :nodoc:
15
16
  attr_reader :value
16
17
  delegate :to_i, :to_f, :to_s, to: :value
17
18
 
@@ -39,11 +40,11 @@ module ActiveSupport
39
40
 
40
41
  def +(other)
41
42
  if Duration === other
42
- seconds = value + other.parts.fetch(:seconds, 0)
43
- new_parts = other.parts.merge(seconds: seconds)
43
+ seconds = value + other._parts.fetch(:seconds, 0)
44
+ new_parts = other._parts.merge(seconds: seconds)
44
45
  new_value = value + other.value
45
46
 
46
- Duration.new(new_value, new_parts)
47
+ Duration.new(new_value, new_parts, other.variable?)
47
48
  else
48
49
  calculate(:+, other)
49
50
  end
@@ -51,12 +52,12 @@ module ActiveSupport
51
52
 
52
53
  def -(other)
53
54
  if Duration === other
54
- seconds = value - other.parts.fetch(:seconds, 0)
55
- new_parts = other.parts.transform_values(&:-@)
55
+ seconds = value - other._parts.fetch(:seconds, 0)
56
+ new_parts = other._parts.transform_values(&:-@)
56
57
  new_parts = new_parts.merge(seconds: seconds)
57
58
  new_value = value - other.value
58
59
 
59
- Duration.new(new_value, new_parts)
60
+ Duration.new(new_value, new_parts, other.variable?)
60
61
  else
61
62
  calculate(:-, other)
62
63
  end
@@ -64,10 +65,10 @@ module ActiveSupport
64
65
 
65
66
  def *(other)
66
67
  if Duration === other
67
- new_parts = other.parts.transform_values { |other_value| value * other_value }
68
+ new_parts = other._parts.transform_values { |other_value| value * other_value }
68
69
  new_value = value * other.value
69
70
 
70
- Duration.new(new_value, new_parts)
71
+ Duration.new(new_value, new_parts, other.variable?)
71
72
  else
72
73
  calculate(:*, other)
73
74
  end
@@ -89,6 +90,10 @@ module ActiveSupport
89
90
  end
90
91
  end
91
92
 
93
+ def variable? # :nodoc:
94
+ false
95
+ end
96
+
92
97
  private
93
98
  def calculate(op, other)
94
99
  if Scalar === other
@@ -123,8 +128,9 @@ module ActiveSupport
123
128
  }.freeze
124
129
 
125
130
  PARTS = [:years, :months, :weeks, :days, :hours, :minutes, :seconds].freeze
131
+ VARIABLE_PARTS = [:years, :months, :weeks, :days].freeze
126
132
 
127
- attr_accessor :value, :parts
133
+ attr_reader :value
128
134
 
129
135
  autoload :ISO8601Parser, "active_support/duration/iso8601_parser"
130
136
  autoload :ISO8601Serializer, "active_support/duration/iso8601_serializer"
@@ -140,38 +146,38 @@ module ActiveSupport
140
146
  new(calculate_total_seconds(parts), parts)
141
147
  end
142
148
 
143
- def ===(other) #:nodoc:
149
+ def ===(other) # :nodoc:
144
150
  other.is_a?(Duration)
145
151
  rescue ::NoMethodError
146
152
  false
147
153
  end
148
154
 
149
- def seconds(value) #:nodoc:
150
- new(value, seconds: value)
155
+ def seconds(value) # :nodoc:
156
+ new(value, { seconds: value }, false)
151
157
  end
152
158
 
153
- def minutes(value) #:nodoc:
154
- new(value * SECONDS_PER_MINUTE, minutes: value)
159
+ def minutes(value) # :nodoc:
160
+ new(value * SECONDS_PER_MINUTE, { minutes: value }, false)
155
161
  end
156
162
 
157
- def hours(value) #:nodoc:
158
- new(value * SECONDS_PER_HOUR, hours: value)
163
+ def hours(value) # :nodoc:
164
+ new(value * SECONDS_PER_HOUR, { hours: value }, false)
159
165
  end
160
166
 
161
- def days(value) #:nodoc:
162
- new(value * SECONDS_PER_DAY, days: value)
167
+ def days(value) # :nodoc:
168
+ new(value * SECONDS_PER_DAY, { days: value }, true)
163
169
  end
164
170
 
165
- def weeks(value) #:nodoc:
166
- new(value * SECONDS_PER_WEEK, weeks: value)
171
+ def weeks(value) # :nodoc:
172
+ new(value * SECONDS_PER_WEEK, { weeks: value }, true)
167
173
  end
168
174
 
169
- def months(value) #:nodoc:
170
- new(value * SECONDS_PER_MONTH, months: value)
175
+ def months(value) # :nodoc:
176
+ new(value * SECONDS_PER_MONTH, { months: value }, true)
171
177
  end
172
178
 
173
- def years(value) #:nodoc:
174
- new(value * SECONDS_PER_YEAR, years: value)
179
+ def years(value) # :nodoc:
180
+ new(value * SECONDS_PER_YEAR, { years: value }, true)
175
181
  end
176
182
 
177
183
  # Creates a new Duration from a seconds value that is converted
@@ -186,19 +192,25 @@ module ActiveSupport
186
192
  end
187
193
 
188
194
  parts = {}
189
- remainder = value.round(9)
195
+ remainder_sign = value <=> 0
196
+ remainder = value.round(9).abs
197
+ variable = false
190
198
 
191
199
  PARTS.each do |part|
192
200
  unless part == :seconds
193
201
  part_in_seconds = PARTS_IN_SECONDS[part]
194
- parts[part] = remainder.div(part_in_seconds)
202
+ parts[part] = remainder.div(part_in_seconds) * remainder_sign
195
203
  remainder %= part_in_seconds
204
+
205
+ unless parts[part].zero?
206
+ variable ||= VARIABLE_PARTS.include?(part)
207
+ end
196
208
  end
197
209
  end unless value == 0
198
210
 
199
- parts[:seconds] = remainder
211
+ parts[:seconds] = remainder * remainder_sign
200
212
 
201
- new(value, parts)
213
+ new(value, parts, variable)
202
214
  end
203
215
 
204
216
  private
@@ -209,12 +221,23 @@ module ActiveSupport
209
221
  end
210
222
  end
211
223
 
212
- def initialize(value, parts) #:nodoc:
224
+ def initialize(value, parts, variable = nil) # :nodoc:
213
225
  @value, @parts = value, parts
214
226
  @parts.reject! { |k, v| v.zero? } unless value == 0
227
+ @parts.freeze
228
+ @variable = variable
229
+
230
+ if @variable.nil?
231
+ @variable = @parts.any? { |part, _| VARIABLE_PARTS.include?(part) }
232
+ end
233
+ end
234
+
235
+ # Returns a copy of the parts hash that defines the duration
236
+ def parts
237
+ @parts.dup
215
238
  end
216
239
 
217
- def coerce(other) #:nodoc:
240
+ def coerce(other) # :nodoc:
218
241
  case other
219
242
  when Scalar
220
243
  [other, self]
@@ -239,13 +262,13 @@ module ActiveSupport
239
262
  # are treated as seconds.
240
263
  def +(other)
241
264
  if Duration === other
242
- parts = @parts.merge(other.parts) do |_key, value, other_value|
265
+ parts = @parts.merge(other._parts) do |_key, value, other_value|
243
266
  value + other_value
244
267
  end
245
- Duration.new(value + other.value, parts)
268
+ Duration.new(value + other.value, parts, @variable || other.variable?)
246
269
  else
247
270
  seconds = @parts.fetch(:seconds, 0) + other
248
- Duration.new(value + other, @parts.merge(seconds: seconds))
271
+ Duration.new(value + other, @parts.merge(seconds: seconds), @variable)
249
272
  end
250
273
  end
251
274
 
@@ -258,9 +281,9 @@ module ActiveSupport
258
281
  # Multiplies this Duration by a Numeric and returns a new Duration.
259
282
  def *(other)
260
283
  if Scalar === other || Duration === other
261
- Duration.new(value * other.value, parts.transform_values { |number| number * other.value })
284
+ Duration.new(value * other.value, @parts.transform_values { |number| number * other.value }, @variable || other.variable?)
262
285
  elsif Numeric === other
263
- Duration.new(value * other, parts.transform_values { |number| number * other })
286
+ Duration.new(value * other, @parts.transform_values { |number| number * other }, @variable)
264
287
  else
265
288
  raise_type_error(other)
266
289
  end
@@ -269,11 +292,11 @@ module ActiveSupport
269
292
  # Divides this Duration by a Numeric and returns a new Duration.
270
293
  def /(other)
271
294
  if Scalar === other
272
- Duration.new(value / other.value, parts.transform_values { |number| number / other.value })
295
+ Duration.new(value / other.value, @parts.transform_values { |number| number / other.value }, @variable)
273
296
  elsif Duration === other
274
297
  value / other.value
275
298
  elsif Numeric === other
276
- Duration.new(value / other, parts.transform_values { |number| number / other })
299
+ Duration.new(value / other, @parts.transform_values { |number| number / other }, @variable)
277
300
  else
278
301
  raise_type_error(other)
279
302
  end
@@ -291,15 +314,15 @@ module ActiveSupport
291
314
  end
292
315
  end
293
316
 
294
- def -@ #:nodoc:
295
- Duration.new(-value, parts.transform_values(&:-@))
317
+ def -@ # :nodoc:
318
+ Duration.new(-value, @parts.transform_values(&:-@), @variable)
296
319
  end
297
320
 
298
- def +@ #:nodoc:
321
+ def +@ # :nodoc:
299
322
  self
300
323
  end
301
324
 
302
- def is_a?(klass) #:nodoc:
325
+ def is_a?(klass) # :nodoc:
303
326
  Duration == klass || value.is_a?(klass)
304
327
  end
305
328
  alias :kind_of? :is_a?
@@ -419,24 +442,24 @@ module ActiveSupport
419
442
  alias :until :ago
420
443
  alias :before :ago
421
444
 
422
- def inspect #:nodoc:
423
- return "#{value} seconds" if parts.empty?
445
+ def inspect # :nodoc:
446
+ return "#{value} seconds" if @parts.empty?
424
447
 
425
- parts.
448
+ @parts.
426
449
  sort_by { |unit, _ | PARTS.index(unit) }.
427
450
  map { |unit, val| "#{val} #{val == 1 ? unit.to_s.chop : unit.to_s}" }.
428
- to_sentence(locale: ::I18n.default_locale)
451
+ to_sentence(locale: false)
429
452
  end
430
453
 
431
- def as_json(options = nil) #:nodoc:
454
+ def as_json(options = nil) # :nodoc:
432
455
  to_i
433
456
  end
434
457
 
435
- def init_with(coder) #:nodoc:
458
+ def init_with(coder) # :nodoc:
436
459
  initialize(coder["value"], coder["parts"])
437
460
  end
438
461
 
439
- def encode_with(coder) #:nodoc:
462
+ def encode_with(coder) # :nodoc:
440
463
  coder.map = { "value" => @value, "parts" => @parts }
441
464
  end
442
465
 
@@ -446,16 +469,24 @@ module ActiveSupport
446
469
  ISO8601Serializer.new(self, precision: precision).serialize
447
470
  end
448
471
 
472
+ def variable? # :nodoc:
473
+ @variable
474
+ end
475
+
476
+ def _parts # :nodoc:
477
+ @parts
478
+ end
479
+
449
480
  private
450
481
  def sum(sign, time = ::Time.current)
451
482
  unless time.acts_like?(:time) || time.acts_like?(:date)
452
483
  raise ::ArgumentError, "expected a time or date, got #{time.inspect}"
453
484
  end
454
485
 
455
- if parts.empty?
486
+ if @parts.empty?
456
487
  time.since(sign * value)
457
488
  else
458
- parts.inject(time) do |t, (type, number)|
489
+ @parts.inject(time) do |t, (type, number)|
459
490
  if type == :seconds
460
491
  t.since(sign * number)
461
492
  elsif type == :minutes
@@ -4,42 +4,105 @@ require "yaml"
4
4
  require "active_support/encrypted_file"
5
5
  require "active_support/ordered_options"
6
6
  require "active_support/core_ext/object/inclusion"
7
+ require "active_support/core_ext/hash/keys"
7
8
  require "active_support/core_ext/module/delegation"
8
9
 
9
10
  module ActiveSupport
11
+ # = Encrypted Configuration
12
+ #
13
+ # Provides convenience methods on top of EncryptedFile to access values stored
14
+ # as encrypted YAML.
15
+ #
16
+ # Values can be accessed via +Hash+ methods, such as +fetch+ and +dig+, or via
17
+ # dynamic accessor methods, similar to OrderedOptions.
18
+ #
19
+ # my_config = ActiveSupport::EncryptedConfiguration.new(...)
20
+ # my_config.read # => "some_secret: 123\nsome_namespace:\n another_secret: 456"
21
+ #
22
+ # my_config[:some_secret]
23
+ # # => 123
24
+ # my_config.some_secret
25
+ # # => 123
26
+ # my_config.dig(:some_namespace, :another_secret)
27
+ # # => 456
28
+ # my_config.some_namespace.another_secret
29
+ # # => 456
30
+ # my_config.fetch(:foo)
31
+ # # => KeyError
32
+ # my_config.foo!
33
+ # # => KeyError
34
+ #
10
35
  class EncryptedConfiguration < EncryptedFile
11
- delegate :[], :fetch, to: :config
36
+ class InvalidContentError < RuntimeError
37
+ def initialize(content_path)
38
+ super "Invalid YAML in '#{content_path}'."
39
+ end
40
+
41
+ def message
42
+ cause.is_a?(Psych::SyntaxError) ? "#{super}\n\n #{cause.message}" : super
43
+ end
44
+ end
45
+
12
46
  delegate_missing_to :options
13
47
 
14
48
  def initialize(config_path:, key_path:, env_key:, raise_if_missing_key:)
15
49
  super content_path: config_path, key_path: key_path,
16
50
  env_key: env_key, raise_if_missing_key: raise_if_missing_key
51
+ @config = nil
52
+ @options = nil
17
53
  end
18
54
 
19
- # Allow a config to be started without a file present
55
+ # Reads the file and returns the decrypted content. See EncryptedFile#read.
20
56
  def read
21
57
  super
22
58
  rescue ActiveSupport::EncryptedFile::MissingContentError
59
+ # Allow a config to be started without a file present
23
60
  ""
24
61
  end
25
62
 
26
- def write(contents)
27
- deserialize(contents)
28
-
29
- super
63
+ def validate! # :nodoc:
64
+ deserialize(read)
30
65
  end
31
66
 
67
+ # Returns the decrypted content as a Hash with symbolized keys.
68
+ #
69
+ # my_config = ActiveSupport::EncryptedConfiguration.new(...)
70
+ # my_config.read # => "some_secret: 123\nsome_namespace:\n another_secret: 456"
71
+ #
72
+ # my_config.config
73
+ # # => { some_secret: 123, some_namespace: { another_secret: 789 } }
74
+ #
32
75
  def config
33
76
  @config ||= deserialize(read).deep_symbolize_keys
34
77
  end
35
78
 
79
+ def inspect # :nodoc:
80
+ "#<#{self.class.name}:#{'%#016x' % (object_id << 1)}>"
81
+ end
82
+
36
83
  private
84
+ def deep_transform(hash)
85
+ return hash unless hash.is_a?(Hash)
86
+
87
+ h = ActiveSupport::OrderedOptions.new
88
+ hash.each do |k, v|
89
+ h[k] = deep_transform(v)
90
+ end
91
+ h
92
+ end
93
+
37
94
  def options
38
- @options ||= ActiveSupport::InheritableOptions.new(config)
95
+ @options ||= deep_transform(config)
39
96
  end
40
97
 
41
- def deserialize(config)
42
- YAML.load(config).presence || {}
98
+ def deserialize(content)
99
+ config = YAML.respond_to?(:unsafe_load) ?
100
+ YAML.unsafe_load(content, filename: content_path) :
101
+ YAML.load(content, filename: content_path)
102
+
103
+ config.presence || {}
104
+ rescue Psych::SyntaxError
105
+ raise InvalidContentError.new(content_path)
43
106
  end
44
107
  end
45
108
  end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "pathname"
4
- require "tmpdir"
4
+ require "tempfile"
5
5
  require "active_support/message_encryptor"
6
6
 
7
7
  module ActiveSupport
@@ -45,10 +45,28 @@ module ActiveSupport
45
45
  @env_key, @raise_if_missing_key = env_key, raise_if_missing_key
46
46
  end
47
47
 
48
+ # Returns the encryption key, first trying the environment variable
49
+ # specified by +env_key+, then trying the key file specified by +key_path+.
50
+ # If +raise_if_missing_key+ is true, raises MissingKeyError if the
51
+ # environment variable is not set and the key file does not exist.
48
52
  def key
49
53
  read_env_key || read_key_file || handle_missing_key
50
54
  end
51
55
 
56
+ # Returns truthy if #key is truthy. Returns falsy otherwise. Unlike #key,
57
+ # does not raise MissingKeyError when +raise_if_missing_key+ is true.
58
+ def key?
59
+ read_env_key || read_key_file
60
+ end
61
+
62
+ # Reads the file and returns the decrypted content.
63
+ #
64
+ # Raises:
65
+ # - MissingKeyError if the key is missing and +raise_if_missing_key+ is true.
66
+ # - MissingContentError if the encrypted file does not exist or otherwise
67
+ # if the key is missing.
68
+ # - ActiveSupport::MessageEncryptor::InvalidMessage if the content cannot be
69
+ # decrypted or verified.
52
70
  def read
53
71
  if !key.nil? && content_path.exist?
54
72
  decrypt content_path.binread
@@ -69,17 +87,16 @@ module ActiveSupport
69
87
 
70
88
  private
71
89
  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
90
+ Tempfile.create(["", "-" + content_path.basename.to_s.chomp(".enc")]) do |tmp_file|
91
+ tmp_path = Pathname.new(tmp_file)
92
+ tmp_path.binwrite contents
75
93
 
76
- yield tmp_path
94
+ yield tmp_path
77
95
 
78
- updated_contents = tmp_path.binread
96
+ updated_contents = tmp_path.binread
79
97
 
80
- write(updated_contents) if updated_contents != contents
81
- ensure
82
- FileUtils.rm(tmp_path) if tmp_path&.exist?
98
+ write(updated_contents) if updated_contents != contents
99
+ end
83
100
  end
84
101
 
85
102
 
@@ -93,17 +110,16 @@ module ActiveSupport
93
110
  end
94
111
 
95
112
  def encryptor
96
- @encryptor ||= ActiveSupport::MessageEncryptor.new([ key ].pack("H*"), cipher: CIPHER)
113
+ @encryptor ||= ActiveSupport::MessageEncryptor.new([ key ].pack("H*"), cipher: CIPHER, serializer: Marshal)
97
114
  end
98
115
 
99
116
 
100
117
  def read_env_key
101
- ENV[env_key]
118
+ ENV[env_key].presence
102
119
  end
103
120
 
104
121
  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?)
122
+ @key_file_contents ||= (key_path.binread.strip if key_path.exist?)
107
123
  end
108
124
 
109
125
  def handle_missing_key
@@ -1,20 +1,40 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "active_support/string_inquirer"
4
+ require "active_support/core_ext/object/inclusion"
4
5
 
5
6
  module ActiveSupport
6
- class EnvironmentInquirer < StringInquirer #:nodoc:
7
- DEFAULT_ENVIRONMENTS = ["development", "test", "production"]
7
+ class EnvironmentInquirer < StringInquirer # :nodoc:
8
+ # Optimization for the three default environments, so this inquirer doesn't need to rely on
9
+ # the slower delegation through method_missing that StringInquirer would normally entail.
10
+ DEFAULT_ENVIRONMENTS = %w[ development test production ]
11
+
12
+ # Environments that'll respond true for #local?
13
+ LOCAL_ENVIRONMENTS = %w[ development test ]
14
+
8
15
  def initialize(env)
16
+ raise(ArgumentError, "'local' is a reserved environment name") if env == "local"
17
+
9
18
  super(env)
10
19
 
11
20
  DEFAULT_ENVIRONMENTS.each do |default|
12
21
  instance_variable_set :"@#{default}", env == default
13
22
  end
23
+
24
+ @local = in? LOCAL_ENVIRONMENTS
14
25
  end
15
26
 
16
27
  DEFAULT_ENVIRONMENTS.each do |env|
17
- class_eval "def #{env}?; @#{env}; end"
28
+ class_eval <<~RUBY, __FILE__, __LINE__ + 1
29
+ def #{env}?
30
+ @#{env}
31
+ end
32
+ RUBY
33
+ end
34
+
35
+ # Returns true if we're in the development or test environment.
36
+ def local?
37
+ @local
18
38
  end
19
39
  end
20
40
  end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveSupport::ErrorReporter::TestHelper # :nodoc:
4
+ class ErrorSubscriber
5
+ attr_reader :events
6
+
7
+ def initialize
8
+ @events = []
9
+ end
10
+
11
+ def report(error, handled:, severity:, source:, context:)
12
+ @events << [error, handled, severity, source, context]
13
+ end
14
+ end
15
+ end