activesupport 7.1.3.2 → 8.0.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 (117) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +74 -1126
  3. data/lib/active_support/array_inquirer.rb +1 -1
  4. data/lib/active_support/backtrace_cleaner.rb +15 -3
  5. data/lib/active_support/benchmark.rb +21 -0
  6. data/lib/active_support/benchmarkable.rb +3 -2
  7. data/lib/active_support/broadcast_logger.rb +19 -18
  8. data/lib/active_support/cache/file_store.rb +27 -12
  9. data/lib/active_support/cache/mem_cache_store.rb +16 -74
  10. data/lib/active_support/cache/memory_store.rb +8 -3
  11. data/lib/active_support/cache/redis_cache_store.rb +21 -15
  12. data/lib/active_support/cache/serializer_with_fallback.rb +0 -23
  13. data/lib/active_support/cache.rb +76 -78
  14. data/lib/active_support/callbacks.rb +79 -116
  15. data/lib/active_support/class_attribute.rb +33 -0
  16. data/lib/active_support/code_generator.rb +24 -10
  17. data/lib/active_support/concurrency/share_lock.rb +0 -1
  18. data/lib/active_support/configuration_file.rb +15 -6
  19. data/lib/active_support/core_ext/array/conversions.rb +3 -5
  20. data/lib/active_support/core_ext/benchmark.rb +6 -9
  21. data/lib/active_support/core_ext/class/attribute.rb +24 -20
  22. data/lib/active_support/core_ext/class/subclasses.rb +15 -35
  23. data/lib/active_support/core_ext/date/blank.rb +4 -0
  24. data/lib/active_support/core_ext/date/conversions.rb +2 -2
  25. data/lib/active_support/core_ext/date_and_time/compatibility.rb +28 -1
  26. data/lib/active_support/core_ext/date_time/blank.rb +4 -0
  27. data/lib/active_support/core_ext/date_time/conversions.rb +0 -4
  28. data/lib/active_support/core_ext/digest/uuid.rb +6 -0
  29. data/lib/active_support/core_ext/enumerable.rb +8 -3
  30. data/lib/active_support/core_ext/erb/util.rb +7 -2
  31. data/lib/active_support/core_ext/hash/except.rb +0 -12
  32. data/lib/active_support/core_ext/hash/keys.rb +4 -4
  33. data/lib/active_support/core_ext/module/attr_internal.rb +16 -6
  34. data/lib/active_support/core_ext/module/delegation.rb +20 -148
  35. data/lib/active_support/core_ext/module/deprecation.rb +1 -4
  36. data/lib/active_support/core_ext/numeric/conversions.rb +3 -3
  37. data/lib/active_support/core_ext/object/blank.rb +45 -1
  38. data/lib/active_support/core_ext/object/duplicable.rb +24 -15
  39. data/lib/active_support/core_ext/object/instance_variables.rb +11 -19
  40. data/lib/active_support/core_ext/object/json.rb +21 -13
  41. data/lib/active_support/core_ext/object/with.rb +5 -3
  42. data/lib/active_support/core_ext/pathname/blank.rb +4 -0
  43. data/lib/active_support/core_ext/range/overlap.rb +1 -1
  44. data/lib/active_support/core_ext/securerandom.rb +4 -4
  45. data/lib/active_support/core_ext/string/conversions.rb +1 -1
  46. data/lib/active_support/core_ext/string/filters.rb +1 -1
  47. data/lib/active_support/core_ext/string/multibyte.rb +1 -1
  48. data/lib/active_support/core_ext/string/output_safety.rb +0 -7
  49. data/lib/active_support/core_ext/thread/backtrace/location.rb +2 -7
  50. data/lib/active_support/core_ext/time/calculations.rb +32 -30
  51. data/lib/active_support/core_ext/time/compatibility.rb +24 -0
  52. data/lib/active_support/core_ext/time/conversions.rb +2 -2
  53. data/lib/active_support/core_ext/time/zones.rb +1 -1
  54. data/lib/active_support/core_ext.rb +0 -1
  55. data/lib/active_support/current_attributes.rb +38 -40
  56. data/lib/active_support/delegation.rb +200 -0
  57. data/lib/active_support/dependencies/autoload.rb +0 -12
  58. data/lib/active_support/dependencies.rb +0 -1
  59. data/lib/active_support/deprecation/constant_accessor.rb +47 -26
  60. data/lib/active_support/deprecation/proxy_wrappers.rb +9 -12
  61. data/lib/active_support/deprecation/reporting.rb +3 -17
  62. data/lib/active_support/deprecation.rb +8 -5
  63. data/lib/active_support/descendants_tracker.rb +9 -87
  64. data/lib/active_support/duration/iso8601_parser.rb +2 -2
  65. data/lib/active_support/duration/iso8601_serializer.rb +1 -2
  66. data/lib/active_support/duration.rb +25 -16
  67. data/lib/active_support/encrypted_configuration.rb +20 -2
  68. data/lib/active_support/encrypted_file.rb +1 -1
  69. data/lib/active_support/error_reporter.rb +65 -3
  70. data/lib/active_support/evented_file_update_checker.rb +0 -2
  71. data/lib/active_support/execution_wrapper.rb +0 -1
  72. data/lib/active_support/file_update_checker.rb +1 -1
  73. data/lib/active_support/fork_tracker.rb +2 -38
  74. data/lib/active_support/gem_version.rb +4 -4
  75. data/lib/active_support/hash_with_indifferent_access.rb +21 -23
  76. data/lib/active_support/html_safe_translation.rb +7 -4
  77. data/lib/active_support/i18n_railtie.rb +19 -11
  78. data/lib/active_support/isolated_execution_state.rb +0 -2
  79. data/lib/active_support/json/encoding.rb +3 -3
  80. data/lib/active_support/log_subscriber.rb +1 -12
  81. data/lib/active_support/logger.rb +15 -2
  82. data/lib/active_support/logger_thread_safe_level.rb +0 -8
  83. data/lib/active_support/message_pack/extensions.rb +15 -2
  84. data/lib/active_support/message_verifier.rb +12 -0
  85. data/lib/active_support/messages/codec.rb +1 -1
  86. data/lib/active_support/multibyte/chars.rb +2 -2
  87. data/lib/active_support/notifications/fanout.rb +4 -8
  88. data/lib/active_support/notifications/instrumenter.rb +32 -21
  89. data/lib/active_support/notifications.rb +28 -27
  90. data/lib/active_support/number_helper/number_converter.rb +2 -2
  91. data/lib/active_support/number_helper.rb +22 -0
  92. data/lib/active_support/option_merger.rb +2 -2
  93. data/lib/active_support/ordered_options.rb +53 -15
  94. data/lib/active_support/railtie.rb +8 -11
  95. data/lib/active_support/string_inquirer.rb +1 -1
  96. data/lib/active_support/subscriber.rb +1 -0
  97. data/lib/active_support/syntax_error_proxy.rb +1 -11
  98. data/lib/active_support/tagged_logging.rb +9 -1
  99. data/lib/active_support/test_case.rb +3 -1
  100. data/lib/active_support/testing/assertions.rb +79 -21
  101. data/lib/active_support/testing/constant_stubbing.rb +30 -8
  102. data/lib/active_support/testing/deprecation.rb +5 -12
  103. data/lib/active_support/testing/isolation.rb +19 -9
  104. data/lib/active_support/testing/method_call_assertions.rb +2 -16
  105. data/lib/active_support/testing/parallelization/server.rb +3 -0
  106. data/lib/active_support/testing/setup_and_teardown.rb +2 -0
  107. data/lib/active_support/testing/strict_warnings.rb +8 -4
  108. data/lib/active_support/testing/tests_without_assertions.rb +19 -0
  109. data/lib/active_support/testing/time_helpers.rb +4 -3
  110. data/lib/active_support/time_with_zone.rb +30 -17
  111. data/lib/active_support/values/time_zone.rb +25 -14
  112. data/lib/active_support/xml_mini.rb +11 -2
  113. data/lib/active_support.rb +12 -4
  114. metadata +68 -19
  115. data/lib/active_support/deprecation/instance_delegator.rb +0 -65
  116. data/lib/active_support/proxy_object.rb +0 -17
  117. data/lib/active_support/ruby_features.rb +0 -7
@@ -42,20 +42,20 @@ class Time
42
42
 
43
43
  # Layers additional behavior on Time.at so that ActiveSupport::TimeWithZone and DateTime
44
44
  # instances can be used when called with a single argument
45
- def at_with_coercion(*args, **kwargs)
46
- return at_without_coercion(*args, **kwargs) if args.size != 1 || !kwargs.empty?
47
-
48
- # Time.at can be called with a time or numerical value
49
- time_or_number = args.first
50
-
51
- if time_or_number.is_a?(ActiveSupport::TimeWithZone)
52
- at_without_coercion(time_or_number.to_r).getlocal
53
- elsif time_or_number.is_a?(DateTime)
54
- at_without_coercion(time_or_number.to_f).getlocal
45
+ def at_with_coercion(time_or_number, *args)
46
+ if args.empty?
47
+ if time_or_number.is_a?(ActiveSupport::TimeWithZone)
48
+ at_without_coercion(time_or_number.to_r).getlocal
49
+ elsif time_or_number.is_a?(DateTime)
50
+ at_without_coercion(time_or_number.to_f).getlocal
51
+ else
52
+ at_without_coercion(time_or_number)
53
+ end
55
54
  else
56
- at_without_coercion(time_or_number)
55
+ at_without_coercion(time_or_number, *args)
57
56
  end
58
57
  end
58
+ ruby2_keywords :at_with_coercion
59
59
  alias_method :at_without_coercion, :at
60
60
  alias_method :at, :at_with_coercion
61
61
 
@@ -108,21 +108,6 @@ class Time
108
108
  subsec
109
109
  end
110
110
 
111
- unless Time.method_defined?(:floor)
112
- def floor(precision = 0)
113
- change(nsec: 0) + subsec.floor(precision)
114
- end
115
- end
116
-
117
- # Restricted Ruby version due to a bug in `Time#ceil`
118
- # See https://bugs.ruby-lang.org/issues/17025 for more details
119
- if RUBY_VERSION <= "2.8"
120
- remove_possible_method :ceil
121
- def ceil(precision = 0)
122
- change(nsec: 0) + subsec.ceil(precision)
123
- end
124
- end
125
-
126
111
  # Returns a new Time where one or more of the elements have been changed according
127
112
  # to the +options+ parameter. The time options (<tt>:hour</tt>, <tt>:min</tt>,
128
113
  # <tt>:sec</tt>, <tt>:usec</tt>, <tt>:nsec</tt>) reset cascadingly, so if only
@@ -159,9 +144,16 @@ class Time
159
144
  ::Time.new(new_year, new_month, new_day, new_hour, new_min, new_sec, new_offset)
160
145
  elsif utc?
161
146
  ::Time.utc(new_year, new_month, new_day, new_hour, new_min, new_sec)
162
- elsif zone&.respond_to?(:utc_to_local)
147
+ elsif zone.respond_to?(:utc_to_local)
163
148
  new_time = ::Time.new(new_year, new_month, new_day, new_hour, new_min, new_sec, zone)
164
149
 
150
+ # Some versions of Ruby have a bug where Time.new with a zone object and
151
+ # fractional seconds will end up with a broken utc_offset.
152
+ # This is fixed in Ruby 3.3.1 and 3.2.4
153
+ unless new_time.utc_offset.integer?
154
+ new_time += 0
155
+ end
156
+
165
157
  # When there are two occurrences of a nominal time due to DST ending,
166
158
  # `Time.new` chooses the first chronological occurrence (the one with a
167
159
  # larger UTC offset). However, for `change`, we want to choose the
@@ -232,8 +224,13 @@ class Time
232
224
  # Returns a new Time representing the time a number of seconds since the instance time
233
225
  def since(seconds)
234
226
  self + seconds
235
- rescue
236
- to_datetime.since(seconds)
227
+ rescue TypeError
228
+ result = to_datetime.since(seconds)
229
+ ActiveSupport.deprecator.warn(
230
+ "Passing an instance of #{seconds.class} to #{self.class}#since is deprecated. This behavior will raise " \
231
+ "a `TypeError` in Rails 8.1."
232
+ )
233
+ result
237
234
  end
238
235
  alias :in :since
239
236
 
@@ -334,7 +331,12 @@ class Time
334
331
  if other.class == Time
335
332
  compare_without_coercion(other)
336
333
  elsif other.is_a?(Time)
337
- compare_without_coercion(other.to_time)
334
+ # also avoid ActiveSupport::TimeWithZone#to_time before Rails 8.0
335
+ if other.respond_to?(:comparable_time)
336
+ compare_without_coercion(other.comparable_time)
337
+ else
338
+ compare_without_coercion(other.to_time)
339
+ end
338
340
  else
339
341
  to_datetime <=> other
340
342
  end
@@ -13,4 +13,28 @@ class Time
13
13
  def to_time
14
14
  preserve_timezone ? self : getlocal
15
15
  end
16
+
17
+ def preserve_timezone # :nodoc:
18
+ system_local_time? || super
19
+ end
20
+
21
+ private
22
+ def system_local_time?
23
+ if ::Time.equal?(self.class)
24
+ zone = self.zone
25
+ String === zone &&
26
+ (zone != "UTC" || active_support_local_zone == "UTC")
27
+ end
28
+ end
29
+
30
+ @@active_support_local_tz = nil
31
+
32
+ def active_support_local_zone
33
+ @@active_support_local_zone = nil if @@active_support_local_tz != ENV["TZ"]
34
+ @@active_support_local_zone ||=
35
+ begin
36
+ @@active_support_local_tz = ENV["TZ"]
37
+ Time.new.zone
38
+ end
39
+ end
16
40
  end
@@ -22,6 +22,7 @@ class Time
22
22
  offset_format = time.formatted_offset(false)
23
23
  time.strftime("%a, %d %b %Y %H:%M:%S #{offset_format}")
24
24
  },
25
+ rfc2822: lambda { |time| time.rfc2822 },
25
26
  iso8601: lambda { |time| time.iso8601 }
26
27
  }
27
28
 
@@ -40,6 +41,7 @@ class Time
40
41
  # time.to_fs(:long) # => "January 18, 2007 06:10"
41
42
  # time.to_fs(:long_ordinal) # => "January 18th, 2007 06:10"
42
43
  # time.to_fs(:rfc822) # => "Thu, 18 Jan 2007 06:10:17 -0600"
44
+ # time.to_fs(:rfc2822) # => "Thu, 18 Jan 2007 06:10:17 -0600"
43
45
  # time.to_fs(:iso8601) # => "2007-01-18T06:10:17-06:00"
44
46
  #
45
47
  # == Adding your own time formats to +to_fs+
@@ -58,8 +60,6 @@ class Time
58
60
  end
59
61
  end
60
62
  alias_method :to_formatted_s, :to_fs
61
- alias_method :to_default_s, :to_s
62
- deprecate to_default_s: :to_s, deprecator: ActiveSupport.deprecator
63
63
 
64
64
  # Returns a formatted string of the offset from UTC, or an alternative
65
65
  # string if the time zone is already UTC.
@@ -20,7 +20,7 @@ class Time
20
20
  # This method accepts any of the following:
21
21
  #
22
22
  # * A \Rails TimeZone object.
23
- # * An identifier for a \Rails TimeZone object (e.g., "Eastern Time (US & Canada)", <tt>-5.hours</tt>).
23
+ # * An identifier for a \Rails TimeZone object (e.g., "Eastern \Time (US & Canada)", <tt>-5.hours</tt>).
24
24
  # * A +TZInfo::Timezone+ object.
25
25
  # * An identifier for a +TZInfo::Timezone+ object (e.g., "America/New_York").
26
26
  #
@@ -1,6 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  Dir.glob(File.expand_path("core_ext/*.rb", __dir__)).sort.each do |path|
4
- next if path.end_with?("core_ext/uri.rb")
5
4
  require path
6
5
  end
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "active_support/callbacks"
4
+ require "active_support/core_ext/object/with"
4
5
  require "active_support/core_ext/enumerable"
5
6
  require "active_support/core_ext/module/delegation"
6
7
 
@@ -94,6 +95,8 @@ module ActiveSupport
94
95
 
95
96
  INVALID_ATTRIBUTE_NAMES = [:set, :reset, :resets, :instance, :before_reset, :after_reset, :reset_all, :clear_all] # :nodoc:
96
97
 
98
+ NOT_SET = Object.new.freeze # :nodoc:
99
+
97
100
  class << self
98
101
  # Returns singleton instance for this class in this thread. If none exists, one is created.
99
102
  def instance
@@ -101,7 +104,14 @@ module ActiveSupport
101
104
  end
102
105
 
103
106
  # Declares one or more attributes that will be given both class and instance accessor methods.
104
- def attribute(*names)
107
+ #
108
+ # ==== Options
109
+ #
110
+ # * <tt>:default</tt> - The default value for the attributes. If the value
111
+ # is a proc or lambda, it will be called whenever an instance is
112
+ # constructed. Otherwise, the value will be duplicated with +#dup+.
113
+ # Default values are re-assigned when the attributes are reset.
114
+ def attribute(*names, default: NOT_SET)
105
115
  invalid_attribute_names = names.map(&:to_sym) & INVALID_ATTRIBUTE_NAMES
106
116
  if invalid_attribute_names.any?
107
117
  raise ArgumentError, "Restricted attribute names: #{invalid_attribute_names.join(", ")}"
@@ -124,22 +134,10 @@ module ActiveSupport
124
134
  end
125
135
  end
126
136
 
127
- ActiveSupport::CodeGenerator.batch(singleton_class, __FILE__, __LINE__) do |owner|
128
- names.each do |name|
129
- owner.define_cached_method(name, namespace: :current_attributes_delegation) do |batch|
130
- batch <<
131
- "def #{name}" <<
132
- "instance.#{name}" <<
133
- "end"
134
- end
135
- owner.define_cached_method("#{name}=", namespace: :current_attributes_delegation) do |batch|
136
- batch <<
137
- "def #{name}=(value)" <<
138
- "instance.#{name} = value" <<
139
- "end"
140
- end
141
- end
142
- end
137
+ Delegation.generate(singleton_class, names, to: :instance, nilable: false, signature: "")
138
+ Delegation.generate(singleton_class, names.map { |n| "#{n}=" }, to: :instance, nilable: false, signature: "value")
139
+
140
+ self.defaults = defaults.merge(names.index_with { default })
143
141
  end
144
142
 
145
143
  # Calls this callback before #reset is called on the instance. Used for resetting external collaborators that depend on current values.
@@ -177,25 +175,29 @@ module ActiveSupport
177
175
  @current_instances_key ||= name.to_sym
178
176
  end
179
177
 
180
- def method_missing(name, *args, &block)
181
- # Caches the method definition as a singleton method of the receiver.
182
- #
183
- # By letting #delegate handle it, we avoid an enclosure that'll capture args.
184
- singleton_class.delegate name, to: :instance
185
-
186
- send(name, *args, &block)
178
+ def method_missing(name, ...)
179
+ instance.public_send(name, ...)
187
180
  end
188
- ruby2_keywords(:method_missing)
189
181
 
190
182
  def respond_to_missing?(name, _)
191
- super || instance.respond_to?(name)
183
+ instance.respond_to?(name) || super
184
+ end
185
+
186
+ def method_added(name)
187
+ super
188
+ return if name == :initialize
189
+ return unless public_method_defined?(name)
190
+ return if respond_to?(name, true)
191
+ Delegation.generate(singleton_class, [name], to: :instance, as: self, nilable: false)
192
192
  end
193
193
  end
194
194
 
195
+ class_attribute :defaults, instance_writer: false, default: {}.freeze
196
+
195
197
  attr_accessor :attributes
196
198
 
197
199
  def initialize
198
- @attributes = {}
200
+ @attributes = resolve_defaults
199
201
  end
200
202
 
201
203
  # Expose one or more attributes within a block. Old values are returned after the block concludes.
@@ -208,28 +210,24 @@ module ActiveSupport
208
210
  # end
209
211
  # end
210
212
  # end
211
- def set(set_attributes)
212
- old_attributes = compute_attributes(set_attributes.keys)
213
- assign_attributes(set_attributes)
214
- yield
215
- ensure
216
- assign_attributes(old_attributes)
213
+ def set(attributes, &block)
214
+ with(**attributes, &block)
217
215
  end
218
216
 
219
217
  # Reset all attributes. Should be called before and after actions, when used as a per-request singleton.
220
218
  def reset
221
219
  run_callbacks :reset do
222
- self.attributes = {}
220
+ self.attributes = resolve_defaults
223
221
  end
224
222
  end
225
223
 
226
224
  private
227
- def assign_attributes(new_attributes)
228
- new_attributes.each { |key, value| public_send("#{key}=", value) }
229
- end
230
-
231
- def compute_attributes(keys)
232
- keys.index_with { |key| public_send(key) }
225
+ def resolve_defaults
226
+ defaults.each_with_object({}) do |(key, value), result|
227
+ if value != NOT_SET
228
+ result[key] = Proc === value ? value.call : value.dup
229
+ end
230
+ end
233
231
  end
234
232
  end
235
233
  end
@@ -0,0 +1,200 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveSupport
4
+ # Error generated by +delegate+ when a method is called on +nil+ and +allow_nil+
5
+ # option is not used.
6
+ class DelegationError < NoMethodError
7
+ class << self
8
+ def nil_target(method_name, target) # :nodoc:
9
+ new("#{method_name} delegated to #{target}, but #{target} is nil")
10
+ end
11
+ end
12
+ end
13
+
14
+ module Delegation # :nodoc:
15
+ RUBY_RESERVED_KEYWORDS = %w(__ENCODING__ __LINE__ __FILE__ alias and BEGIN begin break
16
+ case class def defined? do else elsif END end ensure false for if in module next nil
17
+ not or redo rescue retry return self super then true undef unless until when while yield)
18
+ RESERVED_METHOD_NAMES = (RUBY_RESERVED_KEYWORDS + %w(_ arg args block)).to_set.freeze
19
+
20
+ class << self
21
+ def generate(owner, methods, location: nil, to: nil, prefix: nil, allow_nil: nil, nilable: true, private: nil, as: nil, signature: nil)
22
+ unless to
23
+ raise ArgumentError, "Delegation needs a target. Supply a keyword argument 'to' (e.g. delegate :hello, to: :greeter)."
24
+ end
25
+
26
+ if prefix == true && /^[^a-z_]/.match?(to)
27
+ raise ArgumentError, "Can only automatically set the delegation prefix when delegating to a method."
28
+ end
29
+
30
+ method_prefix = \
31
+ if prefix
32
+ "#{prefix == true ? to : prefix}_"
33
+ else
34
+ ""
35
+ end
36
+
37
+ location ||= caller_locations(1, 1).first
38
+ file, line = location.path, location.lineno
39
+
40
+ receiver = if to.is_a?(Module)
41
+ if to.name.nil?
42
+ raise ArgumentError, "Can't delegate to anonymous class or module: #{to}"
43
+ end
44
+
45
+ unless Inflector.safe_constantize(to.name).equal?(to)
46
+ raise ArgumentError, "Can't delegate to detached class or module: #{to.name}"
47
+ end
48
+
49
+ "::#{to.name}"
50
+ else
51
+ to.to_s
52
+ end
53
+ receiver = "self.#{receiver}" if RESERVED_METHOD_NAMES.include?(receiver)
54
+
55
+ explicit_receiver = false
56
+ receiver_class = if as
57
+ explicit_receiver = true
58
+ as
59
+ elsif to.is_a?(Module)
60
+ to.singleton_class
61
+ elsif receiver == "self.class"
62
+ nilable = false # self.class can't possibly be nil
63
+ owner.singleton_class
64
+ end
65
+
66
+ method_def = []
67
+ method_names = []
68
+
69
+ method_def << "self.private" if private
70
+
71
+ methods.each do |method|
72
+ method_name = prefix ? "#{method_prefix}#{method}" : method
73
+ method_names << method_name.to_sym
74
+
75
+ # Attribute writer methods only accept one argument. Makes sure []=
76
+ # methods still accept two arguments.
77
+ definition = \
78
+ if signature
79
+ signature
80
+ elsif /[^\]]=\z/.match?(method)
81
+ "arg"
82
+ else
83
+ method_object = if receiver_class
84
+ begin
85
+ receiver_class.public_instance_method(method)
86
+ rescue NameError
87
+ raise if explicit_receiver
88
+ # Do nothing. Fall back to `"..."`
89
+ end
90
+ end
91
+
92
+ if method_object
93
+ parameters = method_object.parameters
94
+
95
+ if parameters.map(&:first).intersect?([:opt, :rest, :keyreq, :key, :keyrest])
96
+ "..."
97
+ else
98
+ defn = parameters.filter_map { |type, arg| arg if type == :req }
99
+ defn << "&"
100
+ defn.join(", ")
101
+ end
102
+ else
103
+ "..."
104
+ end
105
+ end
106
+
107
+ # The following generated method calls the target exactly once, storing
108
+ # the returned value in a dummy variable.
109
+ #
110
+ # Reason is twofold: On one hand doing less calls is in general better.
111
+ # On the other hand it could be that the target has side-effects,
112
+ # whereas conceptually, from the user point of view, the delegator should
113
+ # be doing one call.
114
+ if nilable == false
115
+ method_def <<
116
+ "def #{method_name}(#{definition})" <<
117
+ " (#{receiver}).#{method}(#{definition})" <<
118
+ "end"
119
+ elsif allow_nil
120
+ method = method.to_s
121
+
122
+ method_def <<
123
+ "def #{method_name}(#{definition})" <<
124
+ " _ = #{receiver}" <<
125
+ " if !_.nil? || nil.respond_to?(:#{method})" <<
126
+ " _.#{method}(#{definition})" <<
127
+ " end" <<
128
+ "end"
129
+ else
130
+ method = method.to_s
131
+ method_name = method_name.to_s
132
+
133
+ method_def <<
134
+ "def #{method_name}(#{definition})" <<
135
+ " _ = #{receiver}" <<
136
+ " _.#{method}(#{definition})" <<
137
+ "rescue NoMethodError => e" <<
138
+ " if _.nil? && e.name == :#{method}" <<
139
+ " raise ::ActiveSupport::DelegationError.nil_target(:#{method_name}, :'#{receiver}')" <<
140
+ " else" <<
141
+ " raise" <<
142
+ " end" <<
143
+ "end"
144
+ end
145
+ end
146
+ owner.module_eval(method_def.join(";"), file, line)
147
+ method_names
148
+ end
149
+
150
+ def generate_method_missing(owner, target, allow_nil: nil)
151
+ target = target.to_s
152
+ target = "self.#{target}" if RESERVED_METHOD_NAMES.include?(target) || target == "__target"
153
+
154
+ if allow_nil
155
+ owner.module_eval <<~RUBY, __FILE__, __LINE__ + 1
156
+ def respond_to_missing?(name, include_private = false)
157
+ # It may look like an oversight, but we deliberately do not pass
158
+ # +include_private+, because they do not get delegated.
159
+
160
+ return false if name == :marshal_dump || name == :_dump
161
+ #{target}.respond_to?(name) || super
162
+ end
163
+
164
+ def method_missing(method, ...)
165
+ __target = #{target}
166
+ if __target.nil? && !nil.respond_to?(method)
167
+ nil
168
+ elsif __target.respond_to?(method)
169
+ __target.public_send(method, ...)
170
+ else
171
+ super
172
+ end
173
+ end
174
+ RUBY
175
+ else
176
+ owner.module_eval <<~RUBY, __FILE__, __LINE__ + 1
177
+ def respond_to_missing?(name, include_private = false)
178
+ # It may look like an oversight, but we deliberately do not pass
179
+ # +include_private+, because they do not get delegated.
180
+
181
+ return false if name == :marshal_dump || name == :_dump
182
+ #{target}.respond_to?(name) || super
183
+ end
184
+
185
+ def method_missing(method, ...)
186
+ __target = #{target}
187
+ if __target.nil? && !nil.respond_to?(method)
188
+ raise ::ActiveSupport::DelegationError.nil_target(method, :'#{target}')
189
+ elsif __target.respond_to?(method)
190
+ __target.public_send(method, ...)
191
+ else
192
+ super
193
+ end
194
+ end
195
+ RUBY
196
+ end
197
+ end
198
+ end
199
+ end
200
+ end
@@ -27,18 +27,6 @@ module ActiveSupport
27
27
  #
28
28
  # MyLib.eager_load!
29
29
  module Autoload
30
- def self.extended(base) # :nodoc:
31
- if RUBY_VERSION < "3"
32
- base.class_eval do
33
- @_autoloads = nil
34
- @_under_path = nil
35
- @_at_path = nil
36
- @_eager_autoload = false
37
- @_eagerloaded_constants = nil
38
- end
39
- end
40
- end
41
-
42
30
  def autoload(const_name, path = @_at_path)
43
31
  unless path
44
32
  full = [name, @_under_path, const_name.to_s].compact.join("::")
@@ -1,6 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "set"
4
3
  require "active_support/dependencies/interlock"
5
4
 
6
5
  module ActiveSupport # :nodoc:
@@ -2,28 +2,6 @@
2
2
 
3
3
  module ActiveSupport
4
4
  class Deprecation
5
- # DeprecatedConstantAccessor transforms a constant into a deprecated one by
6
- # hooking +const_missing+.
7
- #
8
- # It takes the names of an old (deprecated) constant and of a new constant
9
- # (both in string form) and a deprecator.
10
- #
11
- # The deprecated constant now returns the same object as the new one rather
12
- # than a proxy object, so it can be used transparently in +rescue+ blocks
13
- # etc.
14
- #
15
- # PLANETS = %w(mercury venus earth mars jupiter saturn uranus neptune pluto)
16
- #
17
- # # (In a later update, the original implementation of `PLANETS` has been removed.)
18
- #
19
- # PLANETS_POST_2006 = %w(mercury venus earth mars jupiter saturn uranus neptune)
20
- # include ActiveSupport::Deprecation::DeprecatedConstantAccessor
21
- # deprecate_constant 'PLANETS', 'PLANETS_POST_2006', deprecator: ActiveSupport::Deprecation.new
22
- #
23
- # PLANETS.map { |planet| planet.capitalize }
24
- # # => DEPRECATION WARNING: PLANETS is deprecated! Use PLANETS_POST_2006 instead.
25
- # (Backtrace information…)
26
- # ["Mercury", "Venus", "Earth", "Mars", "Jupiter", "Saturn", "Uranus", "Neptune"]
27
5
  module DeprecatedConstantAccessor
28
6
  def self.included(base)
29
7
  require "active_support/inflector/methods"
@@ -39,11 +17,54 @@ module ActiveSupport
39
17
  super
40
18
  end
41
19
 
42
- def deprecate_constant(const_name, new_constant, message: nil, deprecator: nil)
43
- ActiveSupport.deprecator.warn("DeprecatedConstantAccessor.deprecate_constant without a deprecator is deprecated") unless deprecator
44
- deprecator ||= ActiveSupport::Deprecation._instance
20
+ # Provides a way to rename constants with a deprecation cycle in which
21
+ # both the old and new names work, but using the old one prints a
22
+ # deprecation message.
23
+ #
24
+ # In order to rename <tt>A::B</tt> to <tt>C::D</tt>, you need to delete the
25
+ # definition of <tt>A::B</tt> and declare the deprecation in +A+:
26
+ #
27
+ # require "active_support/deprecation"
28
+ #
29
+ # module A
30
+ # include ActiveSupport::Deprecation::DeprecatedConstantAccessor
31
+ #
32
+ # deprecate_constant "B", "C::D", deprecator: ActiveSupport::Deprecation.new
33
+ # end
34
+ #
35
+ # The first argument is a constant name (no colons). It is the name of
36
+ # the constant you want to deprecate in the enclosing class or module.
37
+ #
38
+ # The second argument is the constant path of the replacement. That
39
+ # has to be a full path even if the replacement is defined in the same
40
+ # namespace as the deprecated one was.
41
+ #
42
+ # In both cases, strings and symbols are supported.
43
+ #
44
+ # The +deprecator+ keyword argument is the object that will print the
45
+ # deprecation message, an instance of ActiveSupport::Deprecation.
46
+ #
47
+ # With that in place, references to <tt>A::B</tt> still work, they
48
+ # evaluate to <tt>C::D</tt> now, and trigger a deprecation warning:
49
+ #
50
+ # DEPRECATION WARNING: A::B is deprecated! Use C::D instead.
51
+ # (called from ...)
52
+ #
53
+ # The message can be customized with the optional +message+ keyword
54
+ # argument.
55
+ #
56
+ # For this to work, a +const_missing+ hook is installed. When client
57
+ # code references the deprecated constant, the callback prints the
58
+ # message and constantizes the replacement.
59
+ #
60
+ # Caveat: If the deprecated constant name is reachable in a different
61
+ # namespace and Ruby constant lookup finds it, the hook won't be
62
+ # called and the deprecation won't work as intended. This may happen,
63
+ # for example, if an ancestor of the enclosing namespace has a
64
+ # constant with the same name. This is an unsupported edge case.
65
+ def deprecate_constant(old_constant_name, new_constant_path, deprecator:, message: nil)
45
66
  class_variable_set(:@@_deprecated_constants, {}) unless class_variable_defined?(:@@_deprecated_constants)
46
- class_variable_get(:@@_deprecated_constants)[const_name.to_s] = { new: new_constant, message: message, deprecator: deprecator }
67
+ class_variable_get(:@@_deprecated_constants)[old_constant_name.to_s] = { new: new_constant_path, message: message, deprecator: deprecator }
47
68
  end
48
69
  end
49
70
  base.singleton_class.prepend extension