activesupport 7.0.4 → 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 (189) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1076 -230
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +6 -6
  5. data/lib/active_support/actionable_error.rb +3 -1
  6. data/lib/active_support/array_inquirer.rb +2 -0
  7. data/lib/active_support/backtrace_cleaner.rb +30 -5
  8. data/lib/active_support/benchmarkable.rb +1 -0
  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 +37 -10
  14. data/lib/active_support/cache/mem_cache_store.rb +100 -76
  15. data/lib/active_support/cache/memory_store.rb +78 -24
  16. data/lib/active_support/cache/null_store.rb +6 -0
  17. data/lib/active_support/cache/redis_cache_store.rb +153 -141
  18. data/lib/active_support/cache/serializer_with_fallback.rb +175 -0
  19. data/lib/active_support/cache/strategy/local_cache.rb +29 -14
  20. data/lib/active_support/cache.rb +333 -253
  21. data/lib/active_support/callbacks.rb +44 -21
  22. data/lib/active_support/code_generator.rb +15 -10
  23. data/lib/active_support/concern.rb +4 -2
  24. data/lib/active_support/concurrency/load_interlock_aware_monitor.rb +42 -3
  25. data/lib/active_support/concurrency/null_lock.rb +13 -0
  26. data/lib/active_support/configurable.rb +10 -0
  27. data/lib/active_support/core_ext/array/conversions.rb +2 -1
  28. data/lib/active_support/core_ext/array.rb +0 -1
  29. data/lib/active_support/core_ext/class/subclasses.rb +13 -10
  30. data/lib/active_support/core_ext/date/calculations.rb +15 -0
  31. data/lib/active_support/core_ext/date/conversions.rb +2 -1
  32. data/lib/active_support/core_ext/date.rb +0 -1
  33. data/lib/active_support/core_ext/date_and_time/calculations.rb +10 -0
  34. data/lib/active_support/core_ext/date_time/calculations.rb +4 -0
  35. data/lib/active_support/core_ext/date_time/conversions.rb +6 -2
  36. data/lib/active_support/core_ext/date_time.rb +0 -1
  37. data/lib/active_support/core_ext/digest/uuid.rb +1 -10
  38. data/lib/active_support/core_ext/enumerable.rb +8 -75
  39. data/lib/active_support/core_ext/erb/util.rb +196 -0
  40. data/lib/active_support/core_ext/hash/conversions.rb +1 -1
  41. data/lib/active_support/core_ext/hash/deep_merge.rb +22 -14
  42. data/lib/active_support/core_ext/hash/deep_transform_values.rb +3 -3
  43. data/lib/active_support/core_ext/hash/keys.rb +3 -3
  44. data/lib/active_support/core_ext/integer/inflections.rb +12 -12
  45. data/lib/active_support/core_ext/module/attribute_accessors.rb +6 -0
  46. data/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb +34 -16
  47. data/lib/active_support/core_ext/module/concerning.rb +6 -6
  48. data/lib/active_support/core_ext/module/delegation.rb +81 -37
  49. data/lib/active_support/core_ext/module/deprecation.rb +15 -12
  50. data/lib/active_support/core_ext/module/introspection.rb +0 -1
  51. data/lib/active_support/core_ext/numeric/bytes.rb +9 -0
  52. data/lib/active_support/core_ext/numeric/conversions.rb +2 -0
  53. data/lib/active_support/core_ext/numeric.rb +0 -1
  54. data/lib/active_support/core_ext/object/deep_dup.rb +16 -0
  55. data/lib/active_support/core_ext/object/duplicable.rb +25 -16
  56. data/lib/active_support/core_ext/object/inclusion.rb +13 -5
  57. data/lib/active_support/core_ext/object/instance_variables.rb +22 -12
  58. data/lib/active_support/core_ext/object/json.rb +16 -6
  59. data/lib/active_support/core_ext/object/to_query.rb +0 -2
  60. data/lib/active_support/core_ext/object/with.rb +44 -0
  61. data/lib/active_support/core_ext/object/with_options.rb +9 -9
  62. data/lib/active_support/core_ext/object.rb +1 -0
  63. data/lib/active_support/core_ext/pathname/blank.rb +16 -0
  64. data/lib/active_support/core_ext/pathname/existence.rb +2 -0
  65. data/lib/active_support/core_ext/pathname.rb +1 -0
  66. data/lib/active_support/core_ext/range/conversions.rb +28 -7
  67. data/lib/active_support/core_ext/range/overlap.rb +40 -0
  68. data/lib/active_support/core_ext/range.rb +1 -2
  69. data/lib/active_support/core_ext/securerandom.rb +24 -12
  70. data/lib/active_support/core_ext/string/filters.rb +20 -14
  71. data/lib/active_support/core_ext/string/indent.rb +1 -1
  72. data/lib/active_support/core_ext/string/inflections.rb +16 -9
  73. data/lib/active_support/core_ext/string/output_safety.rb +42 -174
  74. data/lib/active_support/core_ext/thread/backtrace/location.rb +12 -0
  75. data/lib/active_support/core_ext/time/calculations.rb +22 -2
  76. data/lib/active_support/core_ext/time/conversions.rb +2 -2
  77. data/lib/active_support/core_ext/time/zones.rb +7 -8
  78. data/lib/active_support/core_ext/time.rb +0 -1
  79. data/lib/active_support/current_attributes.rb +15 -6
  80. data/lib/active_support/deep_mergeable.rb +53 -0
  81. data/lib/active_support/dependencies/autoload.rb +17 -12
  82. data/lib/active_support/deprecation/behaviors.rb +65 -42
  83. data/lib/active_support/deprecation/constant_accessor.rb +5 -4
  84. data/lib/active_support/deprecation/deprecators.rb +104 -0
  85. data/lib/active_support/deprecation/disallowed.rb +6 -8
  86. data/lib/active_support/deprecation/instance_delegator.rb +31 -4
  87. data/lib/active_support/deprecation/method_wrappers.rb +6 -23
  88. data/lib/active_support/deprecation/proxy_wrappers.rb +37 -22
  89. data/lib/active_support/deprecation/reporting.rb +43 -26
  90. data/lib/active_support/deprecation.rb +32 -5
  91. data/lib/active_support/deprecator.rb +7 -0
  92. data/lib/active_support/descendants_tracker.rb +104 -132
  93. data/lib/active_support/duration/iso8601_serializer.rb +0 -2
  94. data/lib/active_support/duration.rb +2 -1
  95. data/lib/active_support/encrypted_configuration.rb +63 -11
  96. data/lib/active_support/encrypted_file.rb +16 -12
  97. data/lib/active_support/environment_inquirer.rb +22 -2
  98. data/lib/active_support/error_reporter/test_helper.rb +15 -0
  99. data/lib/active_support/error_reporter.rb +121 -35
  100. data/lib/active_support/evented_file_update_checker.rb +17 -2
  101. data/lib/active_support/execution_wrapper.rb +4 -4
  102. data/lib/active_support/file_update_checker.rb +4 -2
  103. data/lib/active_support/fork_tracker.rb +10 -2
  104. data/lib/active_support/gem_version.rb +4 -4
  105. data/lib/active_support/gzip.rb +2 -0
  106. data/lib/active_support/hash_with_indifferent_access.rb +35 -17
  107. data/lib/active_support/html_safe_translation.rb +16 -6
  108. data/lib/active_support/i18n.rb +1 -1
  109. data/lib/active_support/i18n_railtie.rb +20 -13
  110. data/lib/active_support/inflector/inflections.rb +2 -0
  111. data/lib/active_support/inflector/methods.rb +28 -18
  112. data/lib/active_support/inflector/transliterate.rb +3 -1
  113. data/lib/active_support/isolated_execution_state.rb +26 -22
  114. data/lib/active_support/json/decoding.rb +2 -1
  115. data/lib/active_support/json/encoding.rb +25 -43
  116. data/lib/active_support/key_generator.rb +9 -1
  117. data/lib/active_support/lazy_load_hooks.rb +7 -5
  118. data/lib/active_support/locale/en.yml +2 -0
  119. data/lib/active_support/log_subscriber.rb +85 -33
  120. data/lib/active_support/logger.rb +9 -60
  121. data/lib/active_support/logger_thread_safe_level.rb +10 -24
  122. data/lib/active_support/message_encryptor.rb +197 -53
  123. data/lib/active_support/message_encryptors.rb +141 -0
  124. data/lib/active_support/message_pack/cache_serializer.rb +23 -0
  125. data/lib/active_support/message_pack/extensions.rb +292 -0
  126. data/lib/active_support/message_pack/serializer.rb +63 -0
  127. data/lib/active_support/message_pack.rb +50 -0
  128. data/lib/active_support/message_verifier.rb +212 -93
  129. data/lib/active_support/message_verifiers.rb +135 -0
  130. data/lib/active_support/messages/codec.rb +65 -0
  131. data/lib/active_support/messages/metadata.rb +111 -45
  132. data/lib/active_support/messages/rotation_coordinator.rb +93 -0
  133. data/lib/active_support/messages/rotator.rb +34 -32
  134. data/lib/active_support/messages/serializer_with_fallback.rb +158 -0
  135. data/lib/active_support/multibyte/chars.rb +2 -0
  136. data/lib/active_support/multibyte/unicode.rb +9 -37
  137. data/lib/active_support/notifications/fanout.rb +245 -81
  138. data/lib/active_support/notifications/instrumenter.rb +87 -22
  139. data/lib/active_support/notifications.rb +3 -3
  140. data/lib/active_support/number_helper/number_converter.rb +14 -5
  141. data/lib/active_support/number_helper/number_to_currency_converter.rb +6 -6
  142. data/lib/active_support/number_helper/number_to_human_size_converter.rb +3 -3
  143. data/lib/active_support/number_helper/number_to_phone_converter.rb +1 -0
  144. data/lib/active_support/number_helper.rb +379 -317
  145. data/lib/active_support/ordered_hash.rb +3 -3
  146. data/lib/active_support/ordered_options.rb +14 -0
  147. data/lib/active_support/parameter_filter.rb +103 -84
  148. data/lib/active_support/proxy_object.rb +2 -0
  149. data/lib/active_support/railtie.rb +33 -21
  150. data/lib/active_support/reloader.rb +12 -4
  151. data/lib/active_support/rescuable.rb +2 -0
  152. data/lib/active_support/secure_compare_rotator.rb +16 -9
  153. data/lib/active_support/string_inquirer.rb +3 -1
  154. data/lib/active_support/subscriber.rb +9 -27
  155. data/lib/active_support/syntax_error_proxy.rb +60 -0
  156. data/lib/active_support/tagged_logging.rb +64 -24
  157. data/lib/active_support/test_case.rb +153 -6
  158. data/lib/active_support/testing/assertions.rb +26 -10
  159. data/lib/active_support/testing/autorun.rb +0 -2
  160. data/lib/active_support/testing/constant_stubbing.rb +32 -0
  161. data/lib/active_support/testing/deprecation.rb +25 -25
  162. data/lib/active_support/testing/error_reporter_assertions.rb +107 -0
  163. data/lib/active_support/testing/isolation.rb +29 -28
  164. data/lib/active_support/testing/method_call_assertions.rb +21 -8
  165. data/lib/active_support/testing/parallelize_executor.rb +8 -3
  166. data/lib/active_support/testing/setup_and_teardown.rb +2 -0
  167. data/lib/active_support/testing/stream.rb +1 -1
  168. data/lib/active_support/testing/strict_warnings.rb +39 -0
  169. data/lib/active_support/testing/time_helpers.rb +37 -15
  170. data/lib/active_support/time_with_zone.rb +8 -37
  171. data/lib/active_support/values/time_zone.rb +18 -7
  172. data/lib/active_support/version.rb +1 -1
  173. data/lib/active_support/xml_mini/jdom.rb +3 -10
  174. data/lib/active_support/xml_mini/nokogiri.rb +1 -1
  175. data/lib/active_support/xml_mini/nokogirisax.rb +1 -1
  176. data/lib/active_support/xml_mini/rexml.rb +1 -1
  177. data/lib/active_support/xml_mini.rb +2 -2
  178. data/lib/active_support.rb +14 -3
  179. metadata +148 -19
  180. data/lib/active_support/core_ext/array/deprecated_conversions.rb +0 -25
  181. data/lib/active_support/core_ext/date/deprecated_conversions.rb +0 -26
  182. data/lib/active_support/core_ext/date_time/deprecated_conversions.rb +0 -22
  183. data/lib/active_support/core_ext/numeric/deprecated_conversions.rb +0 -60
  184. data/lib/active_support/core_ext/range/deprecated_conversions.rb +0 -26
  185. data/lib/active_support/core_ext/range/include_time_with_zone.rb +0 -7
  186. data/lib/active_support/core_ext/range/overlaps.rb +0 -10
  187. data/lib/active_support/core_ext/time/deprecated_conversions.rb +0 -22
  188. data/lib/active_support/core_ext/uri.rb +0 -5
  189. data/lib/active_support/per_thread_registry.rb +0 -65
@@ -4,40 +4,87 @@ 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
37
84
  def deep_transform(hash)
38
85
  return hash unless hash.is_a?(Hash)
39
86
 
40
- h = ActiveSupport::InheritableOptions.new
87
+ h = ActiveSupport::OrderedOptions.new
41
88
  hash.each do |k, v|
42
89
  h[k] = deep_transform(v)
43
90
  end
@@ -45,12 +92,17 @@ module ActiveSupport
45
92
  end
46
93
 
47
94
  def options
48
- @options ||= ActiveSupport::InheritableOptions.new(deep_transform(config))
95
+ @options ||= deep_transform(config)
49
96
  end
50
97
 
51
- def deserialize(config)
52
- doc = YAML.respond_to?(:unsafe_load) ? YAML.unsafe_load(config) : YAML.load(config)
53
- doc.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)
54
106
  end
55
107
  end
56
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
@@ -53,6 +53,12 @@ module ActiveSupport
53
53
  read_env_key || read_key_file || handle_missing_key
54
54
  end
55
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
+
56
62
  # Reads the file and returns the decrypted content.
57
63
  #
58
64
  # Raises:
@@ -81,17 +87,16 @@ module ActiveSupport
81
87
 
82
88
  private
83
89
  def writing(contents)
84
- tmp_file = "#{Process.pid}.#{content_path.basename.to_s.chomp('.enc')}"
85
- tmp_path = Pathname.new File.join(Dir.tmpdir, tmp_file)
86
- 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
87
93
 
88
- yield tmp_path
94
+ yield tmp_path
89
95
 
90
- updated_contents = tmp_path.binread
96
+ updated_contents = tmp_path.binread
91
97
 
92
- write(updated_contents) if updated_contents != contents
93
- ensure
94
- FileUtils.rm(tmp_path) if tmp_path&.exist?
98
+ write(updated_contents) if updated_contents != contents
99
+ end
95
100
  end
96
101
 
97
102
 
@@ -105,7 +110,7 @@ module ActiveSupport
105
110
  end
106
111
 
107
112
  def encryptor
108
- @encryptor ||= ActiveSupport::MessageEncryptor.new([ key ].pack("H*"), cipher: CIPHER)
113
+ @encryptor ||= ActiveSupport::MessageEncryptor.new([ key ].pack("H*"), cipher: CIPHER, serializer: Marshal)
109
114
  end
110
115
 
111
116
 
@@ -114,8 +119,7 @@ module ActiveSupport
114
119
  end
115
120
 
116
121
  def read_key_file
117
- return @key_file_contents if defined?(@key_file_contents)
118
- @key_file_contents = (key_path.binread.strip if key_path.exist?)
122
+ @key_file_contents ||= (key_path.binread.strip if key_path.exist?)
119
123
  end
120
124
 
121
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
7
  class EnvironmentInquirer < StringInquirer # :nodoc:
7
- DEFAULT_ENVIRONMENTS = ["development", "test", "production"]
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
@@ -1,9 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActiveSupport
4
+ # = Active Support \Error Reporter
5
+ #
4
6
  # +ActiveSupport::ErrorReporter+ is a common interface for error reporting services.
5
7
  #
6
- # To rescue and report any unhandled error, you can use the +handle+ method:
8
+ # To rescue and report any unhandled error, you can use the #handle method:
7
9
  #
8
10
  # Rails.error.handle do
9
11
  # do_something!
@@ -11,35 +13,19 @@ module ActiveSupport
11
13
  #
12
14
  # If an error is raised, it will be reported and swallowed.
13
15
  #
14
- # Alternatively if you want to report the error but not swallow it, you can use +record+
16
+ # Alternatively, if you want to report the error but not swallow it, you can use #record:
15
17
  #
16
18
  # Rails.error.record do
17
19
  # do_something!
18
20
  # end
19
21
  #
20
- # Both methods can be restricted to only handle a specific exception class
22
+ # Both methods can be restricted to handle only a specific error class:
21
23
  #
22
24
  # maybe_tags = Rails.error.handle(Redis::BaseError) { redis.get("tags") }
23
25
  #
24
- # You can also pass some extra context information that may be used by the error subscribers:
25
- #
26
- # Rails.error.handle(context: { section: "admin" }) do
27
- # # ...
28
- # end
29
- #
30
- # Additionally a +severity+ can be passed along to communicate how important the error report is.
31
- # +severity+ can be one of +:error+, +:warning+, or +:info+. Handled errors default to the +:warning+
32
- # severity, and unhandled ones to +:error+.
33
- #
34
- # Both +handle+ and +record+ pass through the return value from the block. In the case of +handle+
35
- # rescuing an error, a fallback can be provided. The fallback must be a callable whose result will
36
- # be returned when the block raises and is handled:
37
- #
38
- # user = Rails.error.handle(fallback: -> { User.anonymous }) do
39
- # User.find_by(params)
40
- # end
41
26
  class ErrorReporter
42
27
  SEVERITIES = %i(error warning info)
28
+ DEFAULT_SOURCE = "application"
43
29
 
44
30
  attr_accessor :logger
45
31
 
@@ -48,31 +34,92 @@ module ActiveSupport
48
34
  @logger = logger
49
35
  end
50
36
 
51
- # Report any unhandled exception, and swallow it.
37
+ # Evaluates the given block, reporting and swallowing any unhandled error.
38
+ # If no error is raised, returns the return value of the block. Otherwise,
39
+ # returns the result of +fallback.call+, or +nil+ if +fallback+ is not
40
+ # specified.
52
41
  #
42
+ # # Will report a TypeError to all subscribers and return nil.
53
43
  # Rails.error.handle do
54
44
  # 1 + '1'
55
45
  # end
56
46
  #
57
- def handle(error_class = StandardError, severity: :warning, context: {}, fallback: nil)
47
+ # Can be restricted to handle only specific error classes:
48
+ #
49
+ # maybe_tags = Rails.error.handle(Redis::BaseError) { redis.get("tags") }
50
+ #
51
+ # ==== Options
52
+ #
53
+ # * +:severity+ - This value is passed along to subscribers to indicate how
54
+ # important the error report is. Can be +:error+, +:warning+, or +:info+.
55
+ # Defaults to +:warning+.
56
+ #
57
+ # * +:context+ - Extra information that is passed along to subscribers. For
58
+ # example:
59
+ #
60
+ # Rails.error.handle(context: { section: "admin" }) do
61
+ # # ...
62
+ # end
63
+ #
64
+ # * +:fallback+ - A callable that provides +handle+'s return value when an
65
+ # unhandled error is raised. For example:
66
+ #
67
+ # user = Rails.error.handle(fallback: -> { User.anonymous }) do
68
+ # User.find_by(params)
69
+ # end
70
+ #
71
+ # * +:source+ - This value is passed along to subscribers to indicate the
72
+ # source of the error. Subscribers can use this value to ignore certain
73
+ # errors. Defaults to <tt>"application"</tt>.
74
+ def handle(*error_classes, severity: :warning, context: {}, fallback: nil, source: DEFAULT_SOURCE)
75
+ error_classes = [StandardError] if error_classes.blank?
58
76
  yield
59
- rescue error_class => error
60
- report(error, handled: true, severity: severity, context: context)
77
+ rescue *error_classes => error
78
+ report(error, handled: true, severity: severity, context: context, source: source)
61
79
  fallback.call if fallback
62
80
  end
63
81
 
64
- def record(error_class = StandardError, severity: :error, context: {})
82
+ # Evaluates the given block, reporting and re-raising any unhandled error.
83
+ # If no error is raised, returns the return value of the block.
84
+ #
85
+ # # Will report a TypeError to all subscribers and re-raise it.
86
+ # Rails.error.record do
87
+ # 1 + '1'
88
+ # end
89
+ #
90
+ # Can be restricted to handle only specific error classes:
91
+ #
92
+ # tags = Rails.error.record(Redis::BaseError) { redis.get("tags") }
93
+ #
94
+ # ==== Options
95
+ #
96
+ # * +:severity+ - This value is passed along to subscribers to indicate how
97
+ # important the error report is. Can be +:error+, +:warning+, or +:info+.
98
+ # Defaults to +:error+.
99
+ #
100
+ # * +:context+ - Extra information that is passed along to subscribers. For
101
+ # example:
102
+ #
103
+ # Rails.error.record(context: { section: "admin" }) do
104
+ # # ...
105
+ # end
106
+ #
107
+ # * +:source+ - This value is passed along to subscribers to indicate the
108
+ # source of the error. Subscribers can use this value to ignore certain
109
+ # errors. Defaults to <tt>"application"</tt>.
110
+ def record(*error_classes, severity: :error, context: {}, source: DEFAULT_SOURCE)
111
+ error_classes = [StandardError] if error_classes.blank?
65
112
  yield
66
- rescue error_class => error
67
- report(error, handled: false, severity: severity, context: context)
113
+ rescue *error_classes => error
114
+ report(error, handled: false, severity: severity, context: context, source: source)
68
115
  raise
69
116
  end
70
117
 
71
118
  # Register a new error subscriber. The subscriber must respond to
72
119
  #
73
- # report(Exception, handled: Boolean, context: Hash)
120
+ # report(Exception, handled: Boolean, severity: (:error OR :warning OR :info), context: Hash, source: String)
74
121
  #
75
- # The +report+ method +should+ never raise an error.
122
+ # The +report+ method <b>should never</b> raise an error.
76
123
  def subscribe(subscriber)
77
124
  unless subscriber.respond_to?(:report)
78
125
  raise ArgumentError, "Error subscribers must respond to #report"
@@ -80,26 +127,61 @@ module ActiveSupport
80
127
  @subscribers << subscriber
81
128
  end
82
129
 
83
- # Update the execution context that is accessible to error subscribers
130
+ # Unregister an error subscriber. Accepts either a subscriber or a class.
131
+ #
132
+ # subscriber = MyErrorSubscriber.new
133
+ # Rails.error.subscribe(subscriber)
134
+ #
135
+ # Rails.error.unsubscribe(subscriber)
136
+ # # or
137
+ # Rails.error.unsubscribe(MyErrorSubscriber)
138
+ def unsubscribe(subscriber)
139
+ @subscribers.delete_if { |s| subscriber === s }
140
+ end
141
+
142
+ # Prevent a subscriber from being notified of errors for the
143
+ # duration of the block. You may pass in the subscriber itself, or its class.
144
+ #
145
+ # This can be helpful for error reporting service integrations, when they wish
146
+ # to handle any errors higher in the stack.
147
+ def disable(subscriber)
148
+ disabled_subscribers = (ActiveSupport::IsolatedExecutionState[self] ||= [])
149
+ disabled_subscribers << subscriber
150
+ begin
151
+ yield
152
+ ensure
153
+ disabled_subscribers.delete(subscriber)
154
+ end
155
+ end
156
+
157
+ # Update the execution context that is accessible to error subscribers. Any
158
+ # context passed to #handle, #record, or #report will be merged with the
159
+ # context set here.
84
160
  #
85
161
  # Rails.error.set_context(section: "checkout", user_id: @user.id)
86
162
  #
87
- # See +ActiveSupport::ExecutionContext.set+
88
163
  def set_context(...)
89
164
  ActiveSupport::ExecutionContext.set(...)
90
165
  end
91
166
 
92
- # When the block based +handle+ and +record+ methods are not suitable, you can directly use +report+
167
+ # Report an error directly to subscribers. You can use this method when the
168
+ # block-based #handle and #record methods are not suitable.
169
+ #
170
+ # Rails.error.report(error)
93
171
  #
94
- # Rails.error.report(error, handled: true)
95
- def report(error, handled:, severity: handled ? :warning : :error, context: {})
172
+ def report(error, handled: true, severity: handled ? :warning : :error, context: {}, source: DEFAULT_SOURCE)
173
+ return if error.instance_variable_defined?(:@__rails_error_reported)
174
+
96
175
  unless SEVERITIES.include?(severity)
97
176
  raise ArgumentError, "severity must be one of #{SEVERITIES.map(&:inspect).join(", ")}, got: #{severity.inspect}"
98
177
  end
99
178
 
100
179
  full_context = ActiveSupport::ExecutionContext.to_h.merge(context)
180
+ disabled_subscribers = ActiveSupport::IsolatedExecutionState[self]
101
181
  @subscribers.each do |subscriber|
102
- subscriber.report(error, handled: handled, severity: severity, context: full_context)
182
+ unless disabled_subscribers&.any? { |s| s === subscriber }
183
+ subscriber.report(error, handled: handled, severity: severity, context: full_context, source: source)
184
+ end
103
185
  rescue => subscriber_error
104
186
  if logger
105
187
  logger.fatal(
@@ -111,6 +193,10 @@ module ActiveSupport
111
193
  end
112
194
  end
113
195
 
196
+ unless error.frozen?
197
+ error.instance_variable_set(:@__rails_error_reported, true)
198
+ end
199
+
114
200
  nil
115
201
  end
116
202
  end
@@ -1,9 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ gem "listen", "~> 3.5"
4
+ require "listen"
5
+
3
6
  require "set"
4
7
  require "pathname"
5
8
  require "concurrent/atomic/atomic_boolean"
6
- require "listen"
7
9
  require "active_support/fork_tracker"
8
10
 
9
11
  module ActiveSupport
@@ -43,6 +45,10 @@ module ActiveSupport
43
45
  ObjectSpace.define_finalizer(self, @core.finalizer)
44
46
  end
45
47
 
48
+ def inspect
49
+ "#<ActiveSupport::EventedFileUpdateChecker:#{object_id} @files=#{@core.files.to_a.inspect}"
50
+ end
51
+
46
52
  def updated?
47
53
  if @core.restart?
48
54
  @core.thread_safely(&:restart)
@@ -66,7 +72,7 @@ module ActiveSupport
66
72
  end
67
73
 
68
74
  class Core
69
- attr_reader :updated
75
+ attr_reader :updated, :files
70
76
 
71
77
  def initialize(files, dirs)
72
78
  @files = files.map { |file| Pathname(file).expand_path }.to_set
@@ -84,6 +90,10 @@ module ActiveSupport
84
90
  @mutex = Mutex.new
85
91
 
86
92
  start
93
+ # inotify / FSEvents file descriptors are inherited on fork, so
94
+ # we need to reopen them otherwise only the parent or the child
95
+ # will be notified.
96
+ # FIXME: this callback is keeping a reference on the instance
87
97
  @after_fork = ActiveSupport::ForkTracker.after_fork { start }
88
98
  end
89
99
 
@@ -105,6 +115,11 @@ module ActiveSupport
105
115
  @dtw, @missing = [*@dtw, *@missing].partition(&:exist?)
106
116
  @listener = @dtw.any? ? Listen.to(*@dtw, &method(:changed)) : nil
107
117
  @listener&.start
118
+
119
+ # Wait for the listener to be ready to avoid race conditions
120
+ # Unfortunately this isn't quite enough on macOS because the Darwin backend
121
+ # has an extra private thread we can't wait on.
122
+ @listener&.wait_for_state(:processing_events)
108
123
  end
109
124
 
110
125
  def stop
@@ -84,14 +84,14 @@ module ActiveSupport
84
84
  end
85
85
 
86
86
  # Perform the work in the supplied block as an execution.
87
- def self.wrap
87
+ def self.wrap(source: "application.active_support")
88
88
  return yield if active?
89
89
 
90
90
  instance = run!
91
91
  begin
92
92
  yield
93
93
  rescue => error
94
- error_reporter.report(error, handled: false)
94
+ error_reporter&.report(error, handled: false, source: source)
95
95
  raise
96
96
  ensure
97
97
  instance.complete!
@@ -108,8 +108,8 @@ module ActiveSupport
108
108
  end
109
109
  end
110
110
 
111
- def self.error_reporter
112
- @error_reporter ||= ActiveSupport::ErrorReporter.new
111
+ def self.error_reporter # :nodoc:
112
+ ActiveSupport.error_reporter
113
113
  end
114
114
 
115
115
  def self.active_key # :nodoc:
@@ -3,7 +3,9 @@
3
3
  require "active_support/core_ext/time/calculations"
4
4
 
5
5
  module ActiveSupport
6
- # FileUpdateChecker specifies the API used by Rails to watch files
6
+ # = \File Update Checker
7
+ #
8
+ # FileUpdateChecker specifies the API used by \Rails to watch files
7
9
  # and control reloading. The API depends on four methods:
8
10
  #
9
11
  # * +initialize+ which expects two parameters and one block as
@@ -20,7 +22,7 @@ module ActiveSupport
20
22
  # After initialization, a call to +execute_if_updated+ must execute
21
23
  # the block only if there was really a change in the filesystem.
22
24
  #
23
- # This class is used by Rails to reload the I18n framework whenever
25
+ # This class is used by \Rails to reload the I18n framework whenever
24
26
  # they are changed upon a new request.
25
27
  #
26
28
  # i18n_reloader = ActiveSupport::FileUpdateChecker.new(paths) do
@@ -6,7 +6,7 @@ module ActiveSupport
6
6
  def _fork
7
7
  pid = super
8
8
  if pid == 0
9
- ForkTracker.check!
9
+ ForkTracker.after_fork_callback
10
10
  end
11
11
  pid
12
12
  end
@@ -37,7 +37,7 @@ module ActiveSupport
37
37
  @callbacks = []
38
38
 
39
39
  class << self
40
- def check!
40
+ def after_fork_callback
41
41
  new_pid = Process.pid
42
42
  if @pid != new_pid
43
43
  @callbacks.each(&:call)
@@ -45,6 +45,14 @@ module ActiveSupport
45
45
  end
46
46
  end
47
47
 
48
+ if Process.respond_to?(:_fork) # Ruby 3.1+
49
+ def check!
50
+ # We trust the `_fork` callback
51
+ end
52
+ else
53
+ alias_method :check!, :after_fork_callback
54
+ end
55
+
48
56
  def hook!
49
57
  if Process.respond_to?(:_fork) # Ruby 3.1+
50
58
  ::Process.singleton_class.prepend(ModernCoreExt)
@@ -1,16 +1,16 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActiveSupport
4
- # Returns the currently loaded version of Active Support as a <tt>Gem::Version</tt>.
4
+ # Returns the currently loaded version of Active Support as a +Gem::Version+.
5
5
  def self.gem_version
6
6
  Gem::Version.new VERSION::STRING
7
7
  end
8
8
 
9
9
  module VERSION
10
10
  MAJOR = 7
11
- MINOR = 0
12
- TINY = 4
13
- PRE = nil
11
+ MINOR = 1
12
+ TINY = 5
13
+ PRE = "1"
14
14
 
15
15
  STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
16
16
  end
@@ -4,6 +4,8 @@ require "zlib"
4
4
  require "stringio"
5
5
 
6
6
  module ActiveSupport
7
+ # = Active Support \Gzip
8
+ #
7
9
  # A convenient wrapper for the zlib standard library that allows
8
10
  # compression/decompression of strings with gzip.
9
11
  #