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
@@ -5,10 +5,10 @@ class Hash
5
5
  # This includes the values from the root hash and from all
6
6
  # nested hashes and arrays.
7
7
  #
8
- # hash = { person: { name: 'Rob', age: '28' } }
8
+ # hash = { person: { name: 'Rob', age: '28' } }
9
9
  #
10
- # hash.deep_transform_values{ |value| value.to_s.upcase }
11
- # # => {person: {name: "ROB", age: "28"}}
10
+ # hash.deep_transform_values{ |value| value.to_s.upcase }
11
+ # # => {person: {name: "ROB", age: "28"}}
12
12
  def deep_transform_values(&block)
13
13
  _deep_transform_values_in_object(self, &block)
14
14
  end
@@ -3,7 +3,7 @@
3
3
  require "active_support/hash_with_indifferent_access"
4
4
 
5
5
  class Hash
6
- # Returns an <tt>ActiveSupport::HashWithIndifferentAccess</tt> out of its receiver:
6
+ # Returns an ActiveSupport::HashWithIndifferentAccess out of its receiver:
7
7
  #
8
8
  # { a: 1 }.with_indifferent_access['a'] # => 1
9
9
  def with_indifferent_access
@@ -13,8 +13,8 @@ class Hash
13
13
  # Called when object is nested under an object that receives
14
14
  # #with_indifferent_access. This method will be called on the current object
15
15
  # by the enclosing object and is aliased to #with_indifferent_access by
16
- # default. Subclasses of Hash may overwrite this method to return +self+ if
17
- # converting to an <tt>ActiveSupport::HashWithIndifferentAccess</tt> would not be
16
+ # default. Subclasses of Hash may override this method to return +self+ if
17
+ # converting to an ActiveSupport::HashWithIndifferentAccess would not be
18
18
  # desirable.
19
19
  #
20
20
  # b = { b: 1 }
@@ -58,10 +58,10 @@ class Hash
58
58
  # This includes the keys from the root hash and from all
59
59
  # nested hashes and arrays.
60
60
  #
61
- # hash = { person: { name: 'Rob', age: '28' } }
61
+ # hash = { person: { name: 'Rob', age: '28' } }
62
62
  #
63
- # hash.deep_transform_keys{ |key| key.to_s.upcase }
64
- # # => {"PERSON"=>{"NAME"=>"Rob", "AGE"=>"28"}}
63
+ # hash.deep_transform_keys{ |key| key.to_s.upcase }
64
+ # # => {"PERSON"=>{"NAME"=>"Rob", "AGE"=>"28"}}
65
65
  def deep_transform_keys(&block)
66
66
  _deep_transform_keys_in_object(self, &block)
67
67
  end
@@ -116,7 +116,7 @@ class Hash
116
116
  def _deep_transform_keys_in_object(object, &block)
117
117
  case object
118
118
  when Hash
119
- object.each_with_object({}) do |(key, value), result|
119
+ object.each_with_object(self.class.new) do |(key, value), result|
120
120
  result[yield(key)] = _deep_transform_keys_in_object(value, &block)
121
121
  end
122
122
  when Array
@@ -6,12 +6,12 @@ class Integer
6
6
  # Ordinalize turns a number into an ordinal string used to denote the
7
7
  # position in an ordered sequence such as 1st, 2nd, 3rd, 4th.
8
8
  #
9
- # 1.ordinalize # => "1st"
10
- # 2.ordinalize # => "2nd"
11
- # 1002.ordinalize # => "1002nd"
12
- # 1003.ordinalize # => "1003rd"
13
- # -11.ordinalize # => "-11th"
14
- # -1001.ordinalize # => "-1001st"
9
+ # 1.ordinalize # => "1st"
10
+ # 2.ordinalize # => "2nd"
11
+ # 1002.ordinalize # => "1002nd"
12
+ # 1003.ordinalize # => "1003rd"
13
+ # -11.ordinalize # => "-11th"
14
+ # -1001.ordinalize # => "-1001st"
15
15
  def ordinalize
16
16
  ActiveSupport::Inflector.ordinalize(self)
17
17
  end
@@ -19,12 +19,12 @@ class Integer
19
19
  # Ordinal returns the suffix used to denote the position
20
20
  # in an ordered sequence such as 1st, 2nd, 3rd, 4th.
21
21
  #
22
- # 1.ordinal # => "st"
23
- # 2.ordinal # => "nd"
24
- # 1002.ordinal # => "nd"
25
- # 1003.ordinal # => "rd"
26
- # -11.ordinal # => "th"
27
- # -1001.ordinal # => "st"
22
+ # 1.ordinal # => "st"
23
+ # 2.ordinal # => "nd"
24
+ # 1002.ordinal # => "nd"
25
+ # 1003.ordinal # => "rd"
26
+ # -11.ordinal # => "th"
27
+ # -1001.ordinal # => "st"
28
28
  def ordinal
29
29
  ActiveSupport::Inflector.ordinal(self)
30
30
  end
@@ -11,14 +11,14 @@ module Kernel
11
11
  # end
12
12
  #
13
13
  # noisy_call # warning voiced
14
- def silence_warnings
15
- with_warnings(nil) { yield }
14
+ def silence_warnings(&block)
15
+ with_warnings(nil, &block)
16
16
  end
17
17
 
18
18
  # Sets $VERBOSE to +true+ for the duration of the block and back to its
19
19
  # original value afterwards.
20
- def enable_warnings
21
- with_warnings(true) { yield }
20
+ def enable_warnings(&block)
21
+ with_warnings(true, &block)
22
22
  end
23
23
 
24
24
  # Sets $VERBOSE for the duration of the block and back to its original
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Kernel
4
- # class_eval on an object acts like singleton_class.class_eval.
4
+ # class_eval on an object acts like +singleton_class.class_eval+.
5
5
  def class_eval(*args, &block)
6
6
  singleton_class.class_eval(*args, &block)
7
7
  end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # == Attribute Accessors
4
+ #
3
5
  # Extends the module object with class/module and instance accessors for
4
6
  # class/module attributes, just like the native attr* accessors for instance
5
7
  # attributes.
@@ -41,6 +43,7 @@ class Module
41
43
  #
42
44
  # module HairColors
43
45
  # mattr_reader :hair_colors, default: [:brown, :black, :blonde, :red]
46
+ # mattr_reader(:hair_styles) { [:long, :short] }
44
47
  # end
45
48
  #
46
49
  # class Person
@@ -48,6 +51,7 @@ class Module
48
51
  # end
49
52
  #
50
53
  # Person.new.hair_colors # => [:brown, :black, :blonde, :red]
54
+ # Person.new.hair_styles # => [:long, :short]
51
55
  def mattr_reader(*syms, instance_reader: true, instance_accessor: true, default: nil, location: nil)
52
56
  raise TypeError, "module attributes should be defined directly on class, not singleton" if singleton_class?
53
57
  location ||= caller_locations(1, 1).first
@@ -105,6 +109,7 @@ class Module
105
109
  #
106
110
  # module HairColors
107
111
  # mattr_writer :hair_colors, default: [:brown, :black, :blonde, :red]
112
+ # mattr_writer(:hair_styles) { [:long, :short] }
108
113
  # end
109
114
  #
110
115
  # class Person
@@ -112,6 +117,7 @@ class Module
112
117
  # end
113
118
  #
114
119
  # Person.class_variable_get("@@hair_colors") # => [:brown, :black, :blonde, :red]
120
+ # Person.class_variable_get("@@hair_styles") # => [:long, :short]
115
121
  def mattr_writer(*syms, instance_writer: true, instance_accessor: true, default: nil, location: nil)
116
122
  raise TypeError, "module attributes should be defined directly on class, not singleton" if singleton_class?
117
123
  location ||= caller_locations(1, 1).first
@@ -190,6 +196,7 @@ class Module
190
196
  #
191
197
  # module HairColors
192
198
  # mattr_accessor :hair_colors, default: [:brown, :black, :blonde, :red]
199
+ # mattr_accessor(:hair_styles) { [:long, :short] }
193
200
  # end
194
201
  #
195
202
  # class Person
@@ -197,6 +204,7 @@ class Module
197
204
  # end
198
205
  #
199
206
  # Person.class_variable_get("@@hair_colors") # => [:brown, :black, :blonde, :red]
207
+ # Person.class_variable_get("@@hair_styles") # => [:long, :short]
200
208
  def mattr_accessor(*syms, instance_reader: true, instance_writer: true, instance_accessor: true, default: nil, &blk)
201
209
  location = caller_locations(1, 1).first
202
210
  mattr_reader(*syms, instance_reader: instance_reader, instance_accessor: instance_accessor, default: default, location: location, &blk)
@@ -1,11 +1,16 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # == Attribute Accessors per Thread
4
+ #
3
5
  # Extends the module object with class/module and instance accessors for
4
6
  # class/module attributes, just like the native attr* accessors for instance
5
7
  # attributes, but does so on a per-thread basis.
6
8
  #
7
9
  # So the values are scoped within the Thread.current space under the class name
8
10
  # of the module.
11
+ #
12
+ # Note that it can also be scoped per-fiber if +Rails.application.config.active_support.isolation_level+
13
+ # is set to +:fiber+.
9
14
  class Module
10
15
  # Defines a per-thread class attribute and creates class and instance reader methods.
11
16
  # The underlying per-thread class variable is set to +nil+, if it is not previously defined.
@@ -14,9 +19,9 @@ class Module
14
19
  # thread_mattr_reader :user
15
20
  # end
16
21
  #
17
- # Current.user # => nil
18
- # Thread.current[:attr_Current_user] = "DHH"
22
+ # Current.user = "DHH"
19
23
  # Current.user # => "DHH"
24
+ # Thread.new { Current.user }.value # => nil
20
25
  #
21
26
  # The attribute name must be a valid method name in Ruby.
22
27
  #
@@ -37,13 +42,32 @@ class Module
37
42
  syms.each do |sym|
38
43
  raise NameError.new("invalid attribute name: #{sym}") unless /^[_A-Za-z]\w*$/.match?(sym)
39
44
 
40
- # The following generated method concatenates `name` because we want it
41
- # to work with inheritance via polymorphism.
42
- class_eval(<<-EOS, __FILE__, __LINE__ + 1)
43
- def self.#{sym}
44
- Thread.current["attr_" + name + "_#{sym}"]
45
- end
46
- EOS
45
+ # The following generated method concatenates `object_id` because we want
46
+ # subclasses to maintain independent values.
47
+ if default.nil?
48
+ class_eval(<<-EOS, __FILE__, __LINE__ + 1)
49
+ def self.#{sym}
50
+ @__thread_mattr_#{sym} ||= "attr_#{sym}_\#{object_id}"
51
+ ::ActiveSupport::IsolatedExecutionState[@__thread_mattr_#{sym}]
52
+ end
53
+ EOS
54
+ else
55
+ default = default.dup.freeze unless default.frozen?
56
+ singleton_class.define_method("#{sym}_default_value") { default }
57
+
58
+ class_eval(<<-EOS, __FILE__, __LINE__ + 1)
59
+ def self.#{sym}
60
+ @__thread_mattr_#{sym} ||= "attr_#{sym}_\#{object_id}"
61
+ value = ::ActiveSupport::IsolatedExecutionState[@__thread_mattr_#{sym}]
62
+
63
+ if value.nil? && !::ActiveSupport::IsolatedExecutionState.key?(@__thread_mattr_#{sym})
64
+ ::ActiveSupport::IsolatedExecutionState[@__thread_mattr_#{sym}] = #{sym}_default_value
65
+ else
66
+ value
67
+ end
68
+ end
69
+ EOS
70
+ end
47
71
 
48
72
  if instance_reader && instance_accessor
49
73
  class_eval(<<-EOS, __FILE__, __LINE__ + 1)
@@ -52,8 +76,6 @@ class Module
52
76
  end
53
77
  EOS
54
78
  end
55
-
56
- Thread.current["attr_" + name + "_#{sym}"] = default unless default.nil?
57
79
  end
58
80
  end
59
81
  alias :thread_cattr_reader :thread_mattr_reader
@@ -76,15 +98,16 @@ class Module
76
98
  # end
77
99
  #
78
100
  # Current.new.user = "DHH" # => NoMethodError
79
- def thread_mattr_writer(*syms, instance_writer: true, instance_accessor: true, default: nil) # :nodoc:
101
+ def thread_mattr_writer(*syms, instance_writer: true, instance_accessor: true) # :nodoc:
80
102
  syms.each do |sym|
81
103
  raise NameError.new("invalid attribute name: #{sym}") unless /^[_A-Za-z]\w*$/.match?(sym)
82
104
 
83
- # The following generated method concatenates `name` because we want it
84
- # to work with inheritance via polymorphism.
105
+ # The following generated method concatenates `object_id` because we want
106
+ # subclasses to maintain independent values.
85
107
  class_eval(<<-EOS, __FILE__, __LINE__ + 1)
86
108
  def self.#{sym}=(obj)
87
- Thread.current["attr_" + name + "_#{sym}"] = obj
109
+ @__thread_mattr_#{sym} ||= "attr_#{sym}_\#{object_id}"
110
+ ::ActiveSupport::IsolatedExecutionState[@__thread_mattr_#{sym}] = obj
88
111
  end
89
112
  EOS
90
113
 
@@ -95,8 +118,6 @@ class Module
95
118
  end
96
119
  EOS
97
120
  end
98
-
99
- public_send("#{sym}=", default) unless default.nil?
100
121
  end
101
122
  end
102
123
  alias :thread_cattr_writer :thread_mattr_writer
@@ -111,16 +132,18 @@ class Module
111
132
  # Account.user # => "DHH"
112
133
  # Account.new.user # => "DHH"
113
134
  #
135
+ # Unlike +mattr_accessor+, values are *not* shared with subclasses or parent classes.
114
136
  # If a subclass changes the value, the parent class' value is not changed.
115
- # Similarly, if the parent class changes the value, the value of subclasses
116
- # is not changed.
137
+ # If the parent class changes the value, the value of subclasses is not changed.
117
138
  #
118
139
  # class Customer < Account
119
140
  # end
120
141
  #
121
- # Customer.user = "Rafael"
122
- # Customer.user # => "Rafael"
123
- # Account.user # => "DHH"
142
+ # Account.user # => "DHH"
143
+ # Customer.user # => nil
144
+ # Customer.user = "Rafael"
145
+ # Customer.user # => "Rafael"
146
+ # Account.user # => "DHH"
124
147
  #
125
148
  # To omit the instance writer method, pass <tt>instance_writer: false</tt>.
126
149
  # To omit the instance reader method, pass <tt>instance_reader: false</tt>.
@@ -140,6 +163,10 @@ class Module
140
163
  #
141
164
  # Current.new.user = "DHH" # => NoMethodError
142
165
  # Current.new.user # => NoMethodError
166
+ #
167
+ # A default value may be specified using the +:default+ option. Because
168
+ # multiple threads can access the default value, non-frozen default values
169
+ # will be <tt>dup</tt>ed and frozen.
143
170
  def thread_mattr_accessor(*syms, instance_reader: true, instance_writer: true, instance_accessor: true, default: nil)
144
171
  thread_mattr_reader(*syms, instance_reader: instance_reader, instance_accessor: instance_accessor, default: default)
145
172
  thread_mattr_writer(*syms, instance_writer: instance_writer, instance_accessor: instance_accessor)
@@ -3,7 +3,7 @@
3
3
  require "active_support/concern"
4
4
 
5
5
  class Module
6
- # = Bite-sized separation of concerns
6
+ # == Bite-sized separation of concerns
7
7
  #
8
8
  # We often find ourselves with a medium-sized chunk of behavior that we'd
9
9
  # like to extract, but only mix in to a single class.
@@ -18,9 +18,9 @@ class Module
18
18
  # with a comment, as a least-bad alternative. Using modules in separate files
19
19
  # means tedious sifting to get a big-picture view.
20
20
  #
21
- # = Dissatisfying ways to separate small concerns
21
+ # == Dissatisfying ways to separate small concerns
22
22
  #
23
- # == Using comments:
23
+ # === Using comments:
24
24
  #
25
25
  # class Todo < ApplicationRecord
26
26
  # # Other todo implementation
@@ -37,7 +37,7 @@ class Module
37
37
  # end
38
38
  # end
39
39
  #
40
- # == With an inline module:
40
+ # === With an inline module:
41
41
  #
42
42
  # Noisy syntax.
43
43
  #
@@ -61,7 +61,7 @@ class Module
61
61
  # include EventTracking
62
62
  # end
63
63
  #
64
- # == Mix-in noise exiled to its own file:
64
+ # === Mix-in noise exiled to its own file:
65
65
  #
66
66
  # Once our chunk of behavior starts pushing the scroll-to-understand-it
67
67
  # boundary, we give in and move it to a separate file. At this size, the
@@ -75,7 +75,7 @@ class Module
75
75
  # include TodoEventTracking
76
76
  # end
77
77
  #
78
- # = Introducing Module#concerning
78
+ # == Introducing Module#concerning
79
79
  #
80
80
  # By quieting the mix-in noise, we arrive at a natural, low-ceremony way to
81
81
  # separate bite-sized concerns.
@@ -7,9 +7,9 @@ class Module
7
7
  # option is not used.
8
8
  class DelegationError < NoMethodError; end
9
9
 
10
- RUBY_RESERVED_KEYWORDS = %w(alias and BEGIN begin break case class def defined? do
11
- else elsif END end ensure false for if in module next nil not or redo rescue retry
12
- return self super then true undef unless until when while yield)
10
+ RUBY_RESERVED_KEYWORDS = %w(__ENCODING__ __LINE__ __FILE__ alias and BEGIN begin break
11
+ case class def defined? do else elsif END end ensure false for if in module next nil
12
+ not or redo rescue retry return self super then true undef unless until when while yield)
13
13
  DELEGATION_RESERVED_KEYWORDS = %w(_ arg args block)
14
14
  DELEGATION_RESERVED_METHOD_NAMES = Set.new(
15
15
  RUBY_RESERVED_KEYWORDS + DELEGATION_RESERVED_KEYWORDS
@@ -187,25 +187,49 @@ class Module
187
187
  location = caller_locations(1, 1).first
188
188
  file, line = location.path, location.lineno
189
189
 
190
- to = to.to_s
191
- to = "self.#{to}" if DELEGATION_RESERVED_METHOD_NAMES.include?(to)
190
+ receiver = to.to_s
191
+ receiver = "self.#{receiver}" if DELEGATION_RESERVED_METHOD_NAMES.include?(receiver)
192
192
 
193
193
  method_def = []
194
194
  method_names = []
195
195
 
196
- methods.map do |method|
196
+ method_def << "self.private" if private
197
+
198
+ methods.each do |method|
197
199
  method_name = prefix ? "#{method_prefix}#{method}" : method
198
200
  method_names << method_name.to_sym
199
201
 
200
202
  # Attribute writer methods only accept one argument. Makes sure []=
201
203
  # methods still accept two arguments.
202
- definition = if /[^\]]=$/.match?(method)
203
- "arg"
204
- elsif RUBY_VERSION >= "2.7"
205
- "..."
206
- else
207
- "*args, &block"
208
- end
204
+ definition = \
205
+ if /[^\]]=\z/.match?(method)
206
+ "arg"
207
+ else
208
+ method_object =
209
+ begin
210
+ if to.is_a?(Module)
211
+ to.method(method)
212
+ elsif receiver == "self.class"
213
+ method(method)
214
+ end
215
+ rescue NameError
216
+ # Do nothing. Fall back to `"..."`
217
+ end
218
+
219
+ if method_object
220
+ parameters = method_object.parameters
221
+
222
+ if (parameters.map(&:first) & [:opt, :rest, :keyreq, :key, :keyrest]).any?
223
+ "..."
224
+ else
225
+ defn = parameters.filter_map { |type, arg| arg if type == :req }
226
+ defn << "&block"
227
+ defn.join(", ")
228
+ end
229
+ else
230
+ "..."
231
+ end
232
+ end
209
233
 
210
234
  # The following generated method calls the target exactly once, storing
211
235
  # the returned value in a dummy variable.
@@ -219,7 +243,7 @@ class Module
219
243
 
220
244
  method_def <<
221
245
  "def #{method_name}(#{definition})" <<
222
- " _ = #{to}" <<
246
+ " _ = #{receiver}" <<
223
247
  " if !_.nil? || nil.respond_to?(:#{method})" <<
224
248
  " _.#{method}(#{definition})" <<
225
249
  " end" <<
@@ -230,11 +254,11 @@ class Module
230
254
 
231
255
  method_def <<
232
256
  "def #{method_name}(#{definition})" <<
233
- " _ = #{to}" <<
257
+ " _ = #{receiver}" <<
234
258
  " _.#{method}(#{definition})" <<
235
259
  "rescue NoMethodError => e" <<
236
260
  " if _.nil? && e.name == :#{method}" <<
237
- %( raise DelegationError, "#{self}##{method_name} delegated to #{to}.#{method}, but #{to} is nil: \#{self.inspect}") <<
261
+ %( raise DelegationError, "#{self}##{method_name} delegated to #{receiver}.#{method}, but #{receiver} is nil: \#{self.inspect}") <<
238
262
  " else" <<
239
263
  " raise" <<
240
264
  " end" <<
@@ -242,7 +266,6 @@ class Module
242
266
  end
243
267
  end
244
268
  module_eval(method_def.join(";"), file, line)
245
- private(*method_names) if private
246
269
  method_names
247
270
  end
248
271
 
@@ -294,37 +317,52 @@ class Module
294
317
  # of <tt>object</tt> add or remove instance variables.
295
318
  def delegate_missing_to(target, allow_nil: nil)
296
319
  target = target.to_s
297
- target = "self.#{target}" if DELEGATION_RESERVED_METHOD_NAMES.include?(target)
320
+ target = "self.#{target}" if DELEGATION_RESERVED_METHOD_NAMES.include?(target) || target == "__target"
298
321
 
299
- module_eval <<-RUBY, __FILE__, __LINE__ + 1
300
- def respond_to_missing?(name, include_private = false)
301
- # It may look like an oversight, but we deliberately do not pass
302
- # +include_private+, because they do not get delegated.
322
+ if allow_nil
323
+ module_eval <<~RUBY, __FILE__, __LINE__ + 1
324
+ def respond_to_missing?(name, include_private = false)
325
+ # It may look like an oversight, but we deliberately do not pass
326
+ # +include_private+, because they do not get delegated.
303
327
 
304
- return false if name == :marshal_dump || name == :_dump
305
- #{target}.respond_to?(name) || super
306
- end
328
+ return false if name == :marshal_dump || name == :_dump
329
+ #{target}.respond_to?(name) || super
330
+ end
307
331
 
308
- def method_missing(method, *args, &block)
309
- if #{target}.respond_to?(method)
310
- #{target}.public_send(method, *args, &block)
311
- else
312
- begin
332
+ def method_missing(method, *args, &block)
333
+ __target = #{target}
334
+ if __target.nil? && !nil.respond_to?(method)
335
+ nil
336
+ elsif __target.respond_to?(method)
337
+ __target.public_send(method, *args, &block)
338
+ else
313
339
  super
314
- rescue NoMethodError
315
- if #{target}.nil?
316
- if #{allow_nil == true}
317
- nil
318
- else
319
- raise DelegationError, "\#{method} delegated to #{target}, but #{target} is nil"
320
- end
321
- else
322
- raise
323
- end
324
340
  end
325
341
  end
326
- end
327
- ruby2_keywords(:method_missing) if respond_to?(:ruby2_keywords, true)
328
- RUBY
342
+ ruby2_keywords(:method_missing)
343
+ RUBY
344
+ else
345
+ module_eval <<~RUBY, __FILE__, __LINE__ + 1
346
+ def respond_to_missing?(name, include_private = false)
347
+ # It may look like an oversight, but we deliberately do not pass
348
+ # +include_private+, because they do not get delegated.
349
+
350
+ return false if name == :marshal_dump || name == :_dump
351
+ #{target}.respond_to?(name) || super
352
+ end
353
+
354
+ def method_missing(method, *args, &block)
355
+ __target = #{target}
356
+ if __target.nil? && !nil.respond_to?(method)
357
+ raise DelegationError, "\#{method} delegated to #{target}, but #{target} is nil"
358
+ elsif __target.respond_to?(method)
359
+ __target.public_send(method, *args, &block)
360
+ else
361
+ super
362
+ end
363
+ end
364
+ ruby2_keywords(:method_missing)
365
+ RUBY
366
+ end
329
367
  end
330
368
  end
@@ -1,17 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class Module
4
- # deprecate :foo
5
- # deprecate bar: 'message'
6
- # deprecate :foo, :bar, baz: 'warning!', qux: 'gone!'
4
+ # deprecate :foo, deprecator: MyLib.deprecator
5
+ # deprecate :foo, bar: "warning!", deprecator: MyLib.deprecator
7
6
  #
8
- # You can also use custom deprecator instance:
9
- #
10
- # deprecate :foo, deprecator: MyLib::Deprecator.new
11
- # deprecate :foo, bar: "warning!", deprecator: MyLib::Deprecator.new
12
- #
13
- # \Custom deprecators must respond to <tt>deprecation_warning(deprecated_method_name, message, caller_backtrace)</tt>
14
- # method where you can implement your custom warning behavior.
7
+ # A deprecator is typically an instance of ActiveSupport::Deprecation, but you can also pass any object that responds
8
+ # to <tt>deprecation_warning(deprecated_method_name, message, caller_backtrace)</tt> where you can implement your
9
+ # custom warning behavior.
15
10
  #
16
11
  # class MyLib::Deprecator
17
12
  # def deprecation_warning(deprecated_method_name, message, caller_backtrace = nil)
@@ -19,7 +14,15 @@ class Module
19
14
  # Kernel.warn message
20
15
  # end
21
16
  # end
22
- def deprecate(*method_names)
23
- ActiveSupport::Deprecation.deprecate_methods(self, *method_names)
17
+ def deprecate(*method_names, deprecator: nil, **options)
18
+ if deprecator.is_a?(ActiveSupport::Deprecation)
19
+ deprecator.deprecate_methods(self, *method_names, **options)
20
+ elsif deprecator
21
+ # we just need any instance to call deprecate_methods, but the deprecation will be emitted by deprecator
22
+ ActiveSupport.deprecator.deprecate_methods(self, *method_names, **options, deprecator: deprecator)
23
+ else
24
+ ActiveSupport.deprecator.warn("Module.deprecate without a deprecator is deprecated")
25
+ ActiveSupport::Deprecation._instance.deprecate_methods(self, *method_names, **options)
26
+ end
24
27
  end
25
28
  end
@@ -1,6 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "active_support/core_ext/string/filters"
4
3
  require "active_support/inflector"
5
4
 
6
5
  class Module
@@ -53,13 +53,7 @@ class NameError
53
53
  UNBOUND_METHOD_MODULE_NAME = Module.instance_method(:name)
54
54
  private_constant :UNBOUND_METHOD_MODULE_NAME
55
55
 
56
- if UnboundMethod.method_defined?(:bind_call)
57
- def real_mod_name(mod)
58
- UNBOUND_METHOD_MODULE_NAME.bind_call(mod)
59
- end
60
- else
61
- def real_mod_name(mod)
62
- UNBOUND_METHOD_MODULE_NAME.bind(mod).call
63
- end
56
+ def real_mod_name(mod)
57
+ UNBOUND_METHOD_MODULE_NAME.bind_call(mod)
64
58
  end
65
59
  end
@@ -7,6 +7,7 @@ class Numeric
7
7
  TERABYTE = GIGABYTE * 1024
8
8
  PETABYTE = TERABYTE * 1024
9
9
  EXABYTE = PETABYTE * 1024
10
+ ZETTABYTE = EXABYTE * 1024
10
11
 
11
12
  # Enables the use of byte calculations and declarations, like 45.bytes + 2.6.megabytes
12
13
  #
@@ -63,4 +64,12 @@ class Numeric
63
64
  self * EXABYTE
64
65
  end
65
66
  alias :exabyte :exabytes
67
+
68
+ # Returns the number of bytes equivalent to the zettabytes provided.
69
+ #
70
+ # 2.zettabytes # => 2_361_183_241_434_822_606_848
71
+ def zettabytes
72
+ self * ZETTABYTE
73
+ end
74
+ alias :zettabyte :zettabytes
66
75
  end