activesupport 6.0.3.4 → 6.1.7.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 (139) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +456 -398
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +1 -1
  5. data/lib/active_support/array_inquirer.rb +4 -2
  6. data/lib/active_support/backtrace_cleaner.rb +3 -3
  7. data/lib/active_support/benchmarkable.rb +1 -1
  8. data/lib/active_support/cache/file_store.rb +5 -4
  9. data/lib/active_support/cache/mem_cache_store.rb +29 -18
  10. data/lib/active_support/cache/memory_store.rb +46 -26
  11. data/lib/active_support/cache/redis_cache_store.rb +27 -27
  12. data/lib/active_support/cache/strategy/local_cache.rb +21 -6
  13. data/lib/active_support/cache.rb +92 -45
  14. data/lib/active_support/callbacks.rb +65 -56
  15. data/lib/active_support/concern.rb +46 -2
  16. data/lib/active_support/configurable.rb +3 -3
  17. data/lib/active_support/configuration_file.rb +51 -0
  18. data/lib/active_support/core_ext/benchmark.rb +2 -2
  19. data/lib/active_support/core_ext/class/attribute.rb +34 -44
  20. data/lib/active_support/core_ext/class/subclasses.rb +17 -38
  21. data/lib/active_support/core_ext/date/conversions.rb +2 -1
  22. data/lib/active_support/core_ext/date_and_time/calculations.rb +13 -0
  23. data/lib/active_support/core_ext/date_and_time/compatibility.rb +15 -0
  24. data/lib/active_support/core_ext/digest/uuid.rb +1 -0
  25. data/lib/active_support/core_ext/enumerable.rb +76 -4
  26. data/lib/active_support/core_ext/hash/conversions.rb +2 -2
  27. data/lib/active_support/core_ext/hash/deep_transform_values.rb +1 -1
  28. data/lib/active_support/core_ext/hash/except.rb +1 -1
  29. data/lib/active_support/core_ext/hash/keys.rb +1 -1
  30. data/lib/active_support/core_ext/hash/slice.rb +3 -2
  31. data/lib/active_support/core_ext/load_error.rb +1 -1
  32. data/lib/active_support/core_ext/marshal.rb +2 -0
  33. data/lib/active_support/core_ext/module/attr_internal.rb +2 -2
  34. data/lib/active_support/core_ext/module/attribute_accessors.rb +23 -29
  35. data/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb +8 -4
  36. data/lib/active_support/core_ext/module/concerning.rb +8 -2
  37. data/lib/active_support/core_ext/module/delegation.rb +38 -28
  38. data/lib/active_support/core_ext/module/introspection.rb +1 -25
  39. data/lib/active_support/core_ext/name_error.rb +29 -2
  40. data/lib/active_support/core_ext/numeric/conversions.rb +22 -18
  41. data/lib/active_support/core_ext/object/deep_dup.rb +1 -1
  42. data/lib/active_support/core_ext/object/json.rb +13 -2
  43. data/lib/active_support/core_ext/object/try.rb +2 -2
  44. data/lib/active_support/core_ext/range/compare_range.rb +9 -3
  45. data/lib/active_support/core_ext/range/include_time_with_zone.rb +8 -3
  46. data/lib/active_support/core_ext/regexp.rb +8 -1
  47. data/lib/active_support/core_ext/string/access.rb +5 -24
  48. data/lib/active_support/core_ext/string/conversions.rb +1 -0
  49. data/lib/active_support/core_ext/string/inflections.rb +38 -4
  50. data/lib/active_support/core_ext/string/inquiry.rb +1 -0
  51. data/lib/active_support/core_ext/string/multibyte.rb +2 -2
  52. data/lib/active_support/core_ext/string/output_safety.rb +38 -10
  53. data/lib/active_support/core_ext/string/starts_ends_with.rb +2 -2
  54. data/lib/active_support/core_ext/symbol/starts_ends_with.rb +14 -0
  55. data/lib/active_support/core_ext/symbol.rb +3 -0
  56. data/lib/active_support/core_ext/time/calculations.rb +22 -1
  57. data/lib/active_support/core_ext/time/conversions.rb +2 -0
  58. data/lib/active_support/core_ext/uri.rb +5 -1
  59. data/lib/active_support/core_ext.rb +1 -1
  60. data/lib/active_support/current_attributes/test_helper.rb +13 -0
  61. data/lib/active_support/current_attributes.rb +9 -2
  62. data/lib/active_support/dependencies/zeitwerk_integration.rb +4 -1
  63. data/lib/active_support/dependencies.rb +43 -19
  64. data/lib/active_support/deprecation/behaviors.rb +15 -2
  65. data/lib/active_support/deprecation/disallowed.rb +56 -0
  66. data/lib/active_support/deprecation/instance_delegator.rb +0 -1
  67. data/lib/active_support/deprecation/method_wrappers.rb +3 -2
  68. data/lib/active_support/deprecation/proxy_wrappers.rb +3 -3
  69. data/lib/active_support/deprecation/reporting.rb +50 -7
  70. data/lib/active_support/deprecation.rb +6 -1
  71. data/lib/active_support/descendants_tracker.rb +6 -2
  72. data/lib/active_support/digest.rb +2 -0
  73. data/lib/active_support/duration/iso8601_serializer.rb +15 -9
  74. data/lib/active_support/duration.rb +75 -25
  75. data/lib/active_support/encrypted_file.rb +19 -2
  76. data/lib/active_support/environment_inquirer.rb +20 -0
  77. data/lib/active_support/evented_file_update_checker.rb +69 -133
  78. data/lib/active_support/execution_wrapper.rb +16 -13
  79. data/lib/active_support/fork_tracker.rb +64 -0
  80. data/lib/active_support/gem_version.rb +3 -3
  81. data/lib/active_support/hash_with_indifferent_access.rb +48 -24
  82. data/lib/active_support/i18n_railtie.rb +14 -19
  83. data/lib/active_support/inflector/inflections.rb +1 -2
  84. data/lib/active_support/inflector/methods.rb +36 -33
  85. data/lib/active_support/inflector/transliterate.rb +4 -4
  86. data/lib/active_support/json/decoding.rb +4 -4
  87. data/lib/active_support/json/encoding.rb +5 -1
  88. data/lib/active_support/key_generator.rb +1 -1
  89. data/lib/active_support/locale/en.yml +7 -3
  90. data/lib/active_support/log_subscriber.rb +8 -0
  91. data/lib/active_support/logger.rb +1 -1
  92. data/lib/active_support/logger_silence.rb +2 -26
  93. data/lib/active_support/logger_thread_safe_level.rb +34 -12
  94. data/lib/active_support/message_encryptor.rb +4 -7
  95. data/lib/active_support/message_verifier.rb +5 -5
  96. data/lib/active_support/messages/metadata.rb +9 -1
  97. data/lib/active_support/messages/rotation_configuration.rb +2 -1
  98. data/lib/active_support/messages/rotator.rb +6 -5
  99. data/lib/active_support/multibyte/chars.rb +4 -42
  100. data/lib/active_support/multibyte/unicode.rb +9 -83
  101. data/lib/active_support/notifications/fanout.rb +23 -8
  102. data/lib/active_support/notifications/instrumenter.rb +6 -15
  103. data/lib/active_support/notifications.rb +32 -5
  104. data/lib/active_support/number_helper/number_converter.rb +1 -1
  105. data/lib/active_support/number_helper/number_to_currency_converter.rb +3 -7
  106. data/lib/active_support/number_helper/number_to_human_converter.rb +1 -1
  107. data/lib/active_support/number_helper/number_to_human_size_converter.rb +1 -1
  108. data/lib/active_support/number_helper/number_to_rounded_converter.rb +9 -5
  109. data/lib/active_support/number_helper/rounding_helper.rb +12 -28
  110. data/lib/active_support/number_helper.rb +29 -14
  111. data/lib/active_support/option_merger.rb +3 -2
  112. data/lib/active_support/ordered_options.rb +8 -2
  113. data/lib/active_support/parameter_filter.rb +16 -11
  114. data/lib/active_support/per_thread_registry.rb +2 -1
  115. data/lib/active_support/rails.rb +1 -4
  116. data/lib/active_support/railtie.rb +23 -1
  117. data/lib/active_support/reloader.rb +1 -1
  118. data/lib/active_support/rescuable.rb +4 -4
  119. data/lib/active_support/secure_compare_rotator.rb +51 -0
  120. data/lib/active_support/security_utils.rb +19 -12
  121. data/lib/active_support/string_inquirer.rb +4 -2
  122. data/lib/active_support/subscriber.rb +12 -7
  123. data/lib/active_support/tagged_logging.rb +30 -5
  124. data/lib/active_support/testing/assertions.rb +18 -11
  125. data/lib/active_support/testing/parallelization/server.rb +78 -0
  126. data/lib/active_support/testing/parallelization/worker.rb +100 -0
  127. data/lib/active_support/testing/parallelization.rb +12 -95
  128. data/lib/active_support/testing/time_helpers.rb +40 -3
  129. data/lib/active_support/time_with_zone.rb +67 -43
  130. data/lib/active_support/values/time_zone.rb +22 -10
  131. data/lib/active_support/xml_mini/rexml.rb +8 -1
  132. data/lib/active_support.rb +13 -1
  133. metadata +35 -36
  134. data/lib/active_support/core_ext/array/prepend_and_append.rb +0 -5
  135. data/lib/active_support/core_ext/hash/compact.rb +0 -5
  136. data/lib/active_support/core_ext/hash/transform_values.rb +0 -5
  137. data/lib/active_support/core_ext/module/reachable.rb +0 -6
  138. data/lib/active_support/core_ext/numeric/inquiry.rb +0 -5
  139. data/lib/active_support/core_ext/range/include_range.rb +0 -9
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveSupport
4
+ class Deprecation
5
+ module Disallowed
6
+ # Sets the criteria used to identify deprecation messages which should be
7
+ # disallowed. Can be an array containing strings, symbols, or regular
8
+ # expressions. (Symbols are treated as strings). These are compared against
9
+ # the text of the generated deprecation warning.
10
+ #
11
+ # Additionally the scalar symbol +:all+ may be used to treat all
12
+ # deprecations as disallowed.
13
+ #
14
+ # Deprecations matching a substring or regular expression will be handled
15
+ # using the configured +ActiveSupport::Deprecation.disallowed_behavior+
16
+ # rather than +ActiveSupport::Deprecation.behavior+
17
+ attr_writer :disallowed_warnings
18
+
19
+ # Returns the configured criteria used to identify deprecation messages
20
+ # which should be treated as disallowed.
21
+ def disallowed_warnings
22
+ @disallowed_warnings ||= []
23
+ end
24
+
25
+ private
26
+ def deprecation_disallowed?(message)
27
+ disallowed = ActiveSupport::Deprecation.disallowed_warnings
28
+ return false if explicitly_allowed?(message)
29
+ return true if disallowed == :all
30
+ disallowed.any? do |rule|
31
+ case rule
32
+ when String, Symbol
33
+ message.include?(rule.to_s)
34
+ when Regexp
35
+ rule.match?(message)
36
+ end
37
+ end
38
+ end
39
+
40
+ def explicitly_allowed?(message)
41
+ allowances = @explicitly_allowed_warnings.value
42
+ return false unless allowances
43
+ return true if allowances == :all
44
+ allowances = [allowances] unless allowances.kind_of?(Array)
45
+ allowances.any? do |rule|
46
+ case rule
47
+ when String, Symbol
48
+ message.include?(rule.to_s)
49
+ when Regexp
50
+ rule.match?(message)
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
@@ -1,6 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "active_support/core_ext/kernel/singleton_class"
4
3
  require "active_support/core_ext/module/delegation"
5
4
 
6
5
  module ActiveSupport
@@ -56,11 +56,12 @@ module ActiveSupport
56
56
  mod = nil
57
57
 
58
58
  method_names.each do |method_name|
59
+ message = options[method_name]
59
60
  if target_module.method_defined?(method_name) || target_module.private_method_defined?(method_name)
60
61
  method = target_module.instance_method(method_name)
61
62
  target_module.module_eval do
62
63
  redefine_method(method_name) do |*args, &block|
63
- deprecator.deprecation_warning(method_name, options[method_name])
64
+ deprecator.deprecation_warning(method_name, message)
64
65
  method.bind(self).call(*args, &block)
65
66
  end
66
67
  ruby2_keywords(method_name) if respond_to?(:ruby2_keywords, true)
@@ -69,7 +70,7 @@ module ActiveSupport
69
70
  mod ||= Module.new
70
71
  mod.module_eval do
71
72
  define_method(method_name) do |*args, &block|
72
- deprecator.deprecation_warning(method_name, options[method_name])
73
+ deprecator.deprecation_warning(method_name, message)
73
74
  super(*args, &block)
74
75
  end
75
76
  ruby2_keywords(method_name) if respond_to?(:ruby2_keywords, true)
@@ -121,7 +121,7 @@ module ActiveSupport
121
121
  # (Backtrace information…)
122
122
  # ["Mercury", "Venus", "Earth", "Mars", "Jupiter", "Saturn", "Uranus", "Neptune"]
123
123
  class DeprecatedConstantProxy < Module
124
- def self.new(*args, **kwargs, &block)
124
+ def self.new(*args, **options, &block)
125
125
  object = args.first
126
126
 
127
127
  return object unless object
@@ -129,7 +129,7 @@ module ActiveSupport
129
129
  end
130
130
 
131
131
  def initialize(old_const, new_const, deprecator = ActiveSupport::Deprecation.instance, message: "#{old_const} is deprecated! Use #{new_const} instead.")
132
- require "active_support/inflector/methods"
132
+ Kernel.require "active_support/inflector/methods"
133
133
 
134
134
  @old_const = old_const
135
135
  @new_const = new_const
@@ -147,7 +147,7 @@ module ActiveSupport
147
147
 
148
148
  # Don't give a deprecation warning on methods that IRB may invoke
149
149
  # during tab-completion.
150
- delegate :hash, :instance_methods, :name, to: :target
150
+ delegate :hash, :instance_methods, :name, :respond_to?, to: :target
151
151
 
152
152
  # Returns the class of the new constant.
153
153
  #
@@ -6,7 +6,7 @@ module ActiveSupport
6
6
  class Deprecation
7
7
  module Reporting
8
8
  # Whether to print a message (silent mode)
9
- attr_accessor :silenced
9
+ attr_writer :silenced
10
10
  # Name of gem where method is deprecated
11
11
  attr_accessor :gem_name
12
12
 
@@ -20,7 +20,11 @@ module ActiveSupport
20
20
 
21
21
  callstack ||= caller_locations(2)
22
22
  deprecation_message(callstack, message).tap do |m|
23
- behavior.each { |b| b.call(m, callstack, deprecation_horizon, gem_name) }
23
+ if deprecation_disallowed?(message)
24
+ disallowed_behavior.each { |b| b.call(m, callstack, deprecation_horizon, gem_name) }
25
+ else
26
+ behavior.each { |b| b.call(m, callstack, deprecation_horizon, gem_name) }
27
+ end
24
28
  end
25
29
  end
26
30
 
@@ -33,11 +37,50 @@ module ActiveSupport
33
37
  # ActiveSupport::Deprecation.warn('something broke!')
34
38
  # end
35
39
  # # => nil
36
- def silence
37
- old_silenced, @silenced = @silenced, true
38
- yield
39
- ensure
40
- @silenced = old_silenced
40
+ def silence(&block)
41
+ @silenced_thread.bind(true, &block)
42
+ end
43
+
44
+ # Allow previously disallowed deprecation warnings within the block.
45
+ # <tt>allowed_warnings</tt> can be an array containing strings, symbols, or regular
46
+ # expressions. (Symbols are treated as strings). These are compared against
47
+ # the text of deprecation warning messages generated within the block.
48
+ # Matching warnings will be exempt from the rules set by
49
+ # +ActiveSupport::Deprecation.disallowed_warnings+
50
+ #
51
+ # The optional <tt>if:</tt> argument accepts a truthy/falsy value or an object that
52
+ # responds to <tt>.call</tt>. If truthy, then matching warnings will be allowed.
53
+ # If falsey then the method yields to the block without allowing the warning.
54
+ #
55
+ # ActiveSupport::Deprecation.disallowed_behavior = :raise
56
+ # ActiveSupport::Deprecation.disallowed_warnings = [
57
+ # "something broke"
58
+ # ]
59
+ #
60
+ # ActiveSupport::Deprecation.warn('something broke!')
61
+ # # => ActiveSupport::DeprecationException
62
+ #
63
+ # ActiveSupport::Deprecation.allow ['something broke'] do
64
+ # ActiveSupport::Deprecation.warn('something broke!')
65
+ # end
66
+ # # => nil
67
+ #
68
+ # ActiveSupport::Deprecation.allow ['something broke'], if: Rails.env.production? do
69
+ # ActiveSupport::Deprecation.warn('something broke!')
70
+ # end
71
+ # # => ActiveSupport::DeprecationException for dev/test, nil for production
72
+ def allow(allowed_warnings = :all, if: true, &block)
73
+ conditional = binding.local_variable_get(:if)
74
+ conditional = conditional.call if conditional.respond_to?(:call)
75
+ if conditional
76
+ @explicitly_allowed_warnings.bind(allowed_warnings, &block)
77
+ else
78
+ yield
79
+ end
80
+ end
81
+
82
+ def silenced
83
+ @silenced || @silenced_thread.value
41
84
  end
42
85
 
43
86
  def deprecation_warning(deprecated_method_name, message = nil, caller_backtrace = nil)
@@ -17,15 +17,18 @@ module ActiveSupport
17
17
  require "active_support/deprecation/instance_delegator"
18
18
  require "active_support/deprecation/behaviors"
19
19
  require "active_support/deprecation/reporting"
20
+ require "active_support/deprecation/disallowed"
20
21
  require "active_support/deprecation/constant_accessor"
21
22
  require "active_support/deprecation/method_wrappers"
22
23
  require "active_support/deprecation/proxy_wrappers"
23
24
  require "active_support/core_ext/module/deprecation"
25
+ require "concurrent/atomic/thread_local_var"
24
26
 
25
27
  include Singleton
26
28
  include InstanceDelegator
27
29
  include Behavior
28
30
  include Reporting
31
+ include Disallowed
29
32
  include MethodWrapper
30
33
 
31
34
  # The version number in which the deprecated behavior will be removed, by default.
@@ -35,12 +38,14 @@ module ActiveSupport
35
38
  # and the second is a library name.
36
39
  #
37
40
  # ActiveSupport::Deprecation.new('2.0', 'MyLibrary')
38
- def initialize(deprecation_horizon = "6.1", gem_name = "Rails")
41
+ def initialize(deprecation_horizon = "7.0", gem_name = "Rails")
39
42
  self.gem_name = gem_name
40
43
  self.deprecation_horizon = deprecation_horizon
41
44
  # By default, warnings are not silenced and debugging is off.
42
45
  self.silenced = false
43
46
  self.debug = false
47
+ @silenced_thread = Concurrent::ThreadLocalVar.new(false)
48
+ @explicitly_allowed_warnings = Concurrent::ThreadLocalVar.new(nil)
44
49
  end
45
50
  end
46
51
  end
@@ -13,6 +13,7 @@ module ActiveSupport
13
13
  descendants = @@direct_descendants[klass]
14
14
  descendants ? descendants.to_a : []
15
15
  end
16
+ alias_method :subclasses, :direct_descendants
16
17
 
17
18
  def descendants(klass)
18
19
  arr = []
@@ -59,6 +60,7 @@ module ActiveSupport
59
60
  def direct_descendants
60
61
  DescendantsTracker.direct_descendants(self)
61
62
  end
63
+ alias_method :subclasses, :direct_descendants
62
64
 
63
65
  def descendants
64
66
  DescendantsTracker.descendants(self)
@@ -77,15 +79,17 @@ module ActiveSupport
77
79
  end
78
80
 
79
81
  def <<(klass)
80
- cleanup!
81
82
  @refs << WeakRef.new(klass)
82
83
  end
83
84
 
84
85
  def each
85
- @refs.each do |ref|
86
+ @refs.reject! do |ref|
86
87
  yield ref.__getobj__
88
+ false
87
89
  rescue WeakRef::RefError
90
+ true
88
91
  end
92
+ self
89
93
  end
90
94
 
91
95
  def refs_size
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "digest"
4
+
3
5
  module ActiveSupport
4
6
  class Digest #:nodoc:
5
7
  class <<self
@@ -6,6 +6,8 @@ module ActiveSupport
6
6
  class Duration
7
7
  # Serializes duration to string according to ISO 8601 Duration format.
8
8
  class ISO8601Serializer # :nodoc:
9
+ DATE_COMPONENTS = %i(years months days)
10
+
9
11
  def initialize(duration, precision: nil)
10
12
  @duration = duration
11
13
  @precision = precision
@@ -13,14 +15,14 @@ module ActiveSupport
13
15
 
14
16
  # Builds and returns output string.
15
17
  def serialize
16
- parts, sign = normalize
18
+ parts = normalize
17
19
  return "PT0S" if parts.empty?
18
20
 
19
21
  output = +"P"
20
22
  output << "#{parts[:years]}Y" if parts.key?(:years)
21
23
  output << "#{parts[:months]}M" if parts.key?(:months)
22
- output << "#{parts[:weeks]}W" if parts.key?(:weeks)
23
24
  output << "#{parts[:days]}D" if parts.key?(:days)
25
+ output << "#{parts[:weeks]}W" if parts.key?(:weeks)
24
26
  time = +""
25
27
  time << "#{parts[:hours]}H" if parts.key?(:hours)
26
28
  time << "#{parts[:minutes]}M" if parts.key?(:minutes)
@@ -28,7 +30,7 @@ module ActiveSupport
28
30
  time << "#{sprintf(@precision ? "%0.0#{@precision}f" : '%g', parts[:seconds])}S"
29
31
  end
30
32
  output << "T#{time}" unless time.empty?
31
- "#{sign}#{output}"
33
+ output
32
34
  end
33
35
 
34
36
  private
@@ -40,13 +42,17 @@ module ActiveSupport
40
42
  parts = @duration.parts.each_with_object(Hash.new(0)) do |(k, v), p|
41
43
  p[k] += v unless v.zero?
42
44
  end
43
- # If all parts are negative - let's make a negative duration
44
- sign = ""
45
- if parts.values.all? { |v| v < 0 }
46
- sign = "-"
47
- 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
48
49
  end
49
- [parts, sign]
50
+
51
+ parts
52
+ end
53
+
54
+ def week_mixed_with_date?(parts)
55
+ parts.key?(:weeks) && (parts.keys & DATE_COMPONENTS).any?
50
56
  end
51
57
  end
52
58
  end
@@ -39,7 +39,7 @@ module ActiveSupport
39
39
 
40
40
  def +(other)
41
41
  if Duration === other
42
- seconds = value + other.parts[:seconds]
42
+ seconds = value + other.parts.fetch(:seconds, 0)
43
43
  new_parts = other.parts.merge(seconds: seconds)
44
44
  new_value = value + other.value
45
45
 
@@ -51,8 +51,8 @@ module ActiveSupport
51
51
 
52
52
  def -(other)
53
53
  if Duration === other
54
- seconds = value - other.parts[:seconds]
55
- 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(&:-@)
56
56
  new_parts = new_parts.merge(seconds: seconds)
57
57
  new_value = value - other.value
58
58
 
@@ -64,7 +64,7 @@ module ActiveSupport
64
64
 
65
65
  def *(other)
66
66
  if Duration === other
67
- 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 }
68
68
  new_value = value * other.value
69
69
 
70
70
  Duration.new(new_value, new_parts)
@@ -147,31 +147,31 @@ module ActiveSupport
147
147
  end
148
148
 
149
149
  def seconds(value) #:nodoc:
150
- new(value, [[:seconds, value]])
150
+ new(value, seconds: value)
151
151
  end
152
152
 
153
153
  def minutes(value) #:nodoc:
154
- new(value * SECONDS_PER_MINUTE, [[:minutes, value]])
154
+ new(value * SECONDS_PER_MINUTE, minutes: value)
155
155
  end
156
156
 
157
157
  def hours(value) #:nodoc:
158
- new(value * SECONDS_PER_HOUR, [[:hours, value]])
158
+ new(value * SECONDS_PER_HOUR, hours: value)
159
159
  end
160
160
 
161
161
  def days(value) #:nodoc:
162
- new(value * SECONDS_PER_DAY, [[:days, value]])
162
+ new(value * SECONDS_PER_DAY, days: value)
163
163
  end
164
164
 
165
165
  def weeks(value) #:nodoc:
166
- new(value * SECONDS_PER_WEEK, [[:weeks, value]])
166
+ new(value * SECONDS_PER_WEEK, weeks: value)
167
167
  end
168
168
 
169
169
  def months(value) #:nodoc:
170
- new(value * SECONDS_PER_MONTH, [[:months, value]])
170
+ new(value * SECONDS_PER_MONTH, months: value)
171
171
  end
172
172
 
173
173
  def years(value) #:nodoc:
174
- new(value * SECONDS_PER_YEAR, [[:years, value]])
174
+ new(value * SECONDS_PER_YEAR, years: value)
175
175
  end
176
176
 
177
177
  # Creates a new Duration from a seconds value that is converted
@@ -181,18 +181,23 @@ module ActiveSupport
181
181
  # ActiveSupport::Duration.build(2716146).parts # => {:months=>1, :days=>1}
182
182
  #
183
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
+
184
188
  parts = {}
185
- remainder = value.round(9)
189
+ remainder_sign = value <=> 0
190
+ remainder = value.round(9).abs
186
191
 
187
192
  PARTS.each do |part|
188
193
  unless part == :seconds
189
194
  part_in_seconds = PARTS_IN_SECONDS[part]
190
- parts[part] = remainder.div(part_in_seconds)
195
+ parts[part] = remainder.div(part_in_seconds) * remainder_sign
191
196
  remainder %= part_in_seconds
192
197
  end
193
198
  end unless value == 0
194
199
 
195
- parts[:seconds] = remainder
200
+ parts[:seconds] = remainder * remainder_sign
196
201
 
197
202
  new(value, parts)
198
203
  end
@@ -206,8 +211,7 @@ module ActiveSupport
206
211
  end
207
212
 
208
213
  def initialize(value, parts) #:nodoc:
209
- @value, @parts = value, parts.to_h
210
- @parts.default = 0
214
+ @value, @parts = value, parts
211
215
  @parts.reject! { |k, v| v.zero? } unless value == 0
212
216
  end
213
217
 
@@ -236,13 +240,12 @@ module ActiveSupport
236
240
  # are treated as seconds.
237
241
  def +(other)
238
242
  if Duration === other
239
- parts = @parts.dup
240
- other.parts.each do |(key, value)|
241
- parts[key] += value
243
+ parts = @parts.merge(other.parts) do |_key, value, other_value|
244
+ value + other_value
242
245
  end
243
246
  Duration.new(value + other.value, parts)
244
247
  else
245
- seconds = @parts[:seconds] + other
248
+ seconds = @parts.fetch(:seconds, 0) + other
246
249
  Duration.new(value + other, @parts.merge(seconds: seconds))
247
250
  end
248
251
  end
@@ -256,9 +259,9 @@ module ActiveSupport
256
259
  # Multiplies this Duration by a Numeric and returns a new Duration.
257
260
  def *(other)
258
261
  if Scalar === other || Duration === other
259
- Duration.new(value * other.value, parts.map { |type, number| [type, number * other.value] })
262
+ Duration.new(value * other.value, parts.transform_values { |number| number * other.value })
260
263
  elsif Numeric === other
261
- Duration.new(value * other, parts.map { |type, number| [type, number * other] })
264
+ Duration.new(value * other, parts.transform_values { |number| number * other })
262
265
  else
263
266
  raise_type_error(other)
264
267
  end
@@ -267,11 +270,11 @@ module ActiveSupport
267
270
  # Divides this Duration by a Numeric and returns a new Duration.
268
271
  def /(other)
269
272
  if Scalar === other
270
- Duration.new(value / other.value, parts.map { |type, number| [type, number / other.value] })
273
+ Duration.new(value / other.value, parts.transform_values { |number| number / other.value })
271
274
  elsif Duration === other
272
275
  value / other.value
273
276
  elsif Numeric === other
274
- Duration.new(value / other, parts.map { |type, number| [type, number / other] })
277
+ Duration.new(value / other, parts.transform_values { |number| number / other })
275
278
  else
276
279
  raise_type_error(other)
277
280
  end
@@ -290,7 +293,11 @@ module ActiveSupport
290
293
  end
291
294
 
292
295
  def -@ #:nodoc:
293
- Duration.new(-value, parts.map { |type, number| [type, -number] })
296
+ Duration.new(-value, parts.transform_values(&:-@))
297
+ end
298
+
299
+ def +@ #:nodoc:
300
+ self
294
301
  end
295
302
 
296
303
  def is_a?(klass) #:nodoc:
@@ -343,6 +350,49 @@ module ActiveSupport
343
350
  def to_i
344
351
  @value.to_i
345
352
  end
353
+ alias :in_seconds :to_i
354
+
355
+ # Returns the amount of minutes a duration covers as a float
356
+ #
357
+ # 1.day.in_minutes # => 1440.0
358
+ def in_minutes
359
+ in_seconds / SECONDS_PER_MINUTE.to_f
360
+ end
361
+
362
+ # Returns the amount of hours a duration covers as a float
363
+ #
364
+ # 1.day.in_hours # => 24.0
365
+ def in_hours
366
+ in_seconds / SECONDS_PER_HOUR.to_f
367
+ end
368
+
369
+ # Returns the amount of days a duration covers as a float
370
+ #
371
+ # 12.hours.in_days # => 0.5
372
+ def in_days
373
+ in_seconds / SECONDS_PER_DAY.to_f
374
+ end
375
+
376
+ # Returns the amount of weeks a duration covers as a float
377
+ #
378
+ # 2.months.in_weeks # => 8.696
379
+ def in_weeks
380
+ in_seconds / SECONDS_PER_WEEK.to_f
381
+ end
382
+
383
+ # Returns the amount of months a duration covers as a float
384
+ #
385
+ # 9.weeks.in_months # => 2.07
386
+ def in_months
387
+ in_seconds / SECONDS_PER_MONTH.to_f
388
+ end
389
+
390
+ # Returns the amount of years a duration covers as a float
391
+ #
392
+ # 30.days.in_years # => 0.082
393
+ def in_years
394
+ in_seconds / SECONDS_PER_YEAR.to_f
395
+ end
346
396
 
347
397
  # Returns +true+ if +other+ is also a Duration instance, which has the
348
398
  # same parts as this one.
@@ -20,17 +20,28 @@ module ActiveSupport
20
20
  end
21
21
  end
22
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
+
23
29
  CIPHER = "aes-128-gcm"
24
30
 
25
31
  def self.generate_key
26
32
  SecureRandom.hex(ActiveSupport::MessageEncryptor.key_len(CIPHER))
27
33
  end
28
34
 
35
+ def self.expected_key_length # :nodoc:
36
+ @expected_key_length ||= generate_key.length
37
+ end
38
+
29
39
 
30
40
  attr_reader :content_path, :key_path, :env_key, :raise_if_missing_key
31
41
 
32
42
  def initialize(content_path:, key_path:, env_key:, raise_if_missing_key:)
33
- @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)
34
45
  @env_key, @raise_if_missing_key = env_key, raise_if_missing_key
35
46
  end
36
47
 
@@ -73,6 +84,7 @@ module ActiveSupport
73
84
 
74
85
 
75
86
  def encrypt(contents)
87
+ check_key_length
76
88
  encryptor.encrypt_and_sign contents
77
89
  end
78
90
 
@@ -90,11 +102,16 @@ module ActiveSupport
90
102
  end
91
103
 
92
104
  def read_key_file
93
- 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?)
94
107
  end
95
108
 
96
109
  def handle_missing_key
97
110
  raise MissingKeyError.new(key_path: key_path, env_key: env_key) if raise_if_missing_key
98
111
  end
112
+
113
+ def check_key_length
114
+ raise InvalidKeyLengthError if key&.length != self.class.expected_key_length
115
+ end
99
116
  end
100
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