activesupport 7.1.4.1 → 7.2.2.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 (96) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +127 -1129
  3. data/lib/active_support/array_inquirer.rb +1 -1
  4. data/lib/active_support/backtrace_cleaner.rb +10 -3
  5. data/lib/active_support/broadcast_logger.rb +18 -18
  6. data/lib/active_support/cache/file_store.rb +15 -10
  7. data/lib/active_support/cache/mem_cache_store.rb +16 -74
  8. data/lib/active_support/cache/memory_store.rb +2 -1
  9. data/lib/active_support/cache/redis_cache_store.rb +16 -13
  10. data/lib/active_support/cache/serializer_with_fallback.rb +0 -23
  11. data/lib/active_support/cache.rb +60 -68
  12. data/lib/active_support/callbacks.rb +74 -113
  13. data/lib/active_support/core_ext/array/conversions.rb +0 -2
  14. data/lib/active_support/core_ext/class/subclasses.rb +15 -35
  15. data/lib/active_support/core_ext/date/blank.rb +4 -0
  16. data/lib/active_support/core_ext/date/conversions.rb +0 -2
  17. data/lib/active_support/core_ext/date_and_time/compatibility.rb +28 -1
  18. data/lib/active_support/core_ext/date_time/blank.rb +4 -0
  19. data/lib/active_support/core_ext/date_time/conversions.rb +0 -4
  20. data/lib/active_support/core_ext/digest/uuid.rb +6 -0
  21. data/lib/active_support/core_ext/erb/util.rb +5 -0
  22. data/lib/active_support/core_ext/hash/keys.rb +4 -4
  23. data/lib/active_support/core_ext/module/attr_internal.rb +17 -6
  24. data/lib/active_support/core_ext/module/delegation.rb +20 -163
  25. data/lib/active_support/core_ext/module/deprecation.rb +1 -4
  26. data/lib/active_support/core_ext/numeric/conversions.rb +3 -3
  27. data/lib/active_support/core_ext/object/blank.rb +45 -1
  28. data/lib/active_support/core_ext/object/instance_variables.rb +11 -19
  29. data/lib/active_support/core_ext/object/json.rb +6 -4
  30. data/lib/active_support/core_ext/object/with.rb +5 -3
  31. data/lib/active_support/core_ext/pathname/blank.rb +4 -0
  32. data/lib/active_support/core_ext/range/overlap.rb +1 -1
  33. data/lib/active_support/core_ext/securerandom.rb +8 -24
  34. data/lib/active_support/core_ext/string/conversions.rb +1 -1
  35. data/lib/active_support/core_ext/string/filters.rb +1 -1
  36. data/lib/active_support/core_ext/string/multibyte.rb +1 -1
  37. data/lib/active_support/core_ext/string/output_safety.rb +0 -7
  38. data/lib/active_support/core_ext/time/calculations.rb +18 -28
  39. data/lib/active_support/core_ext/time/compatibility.rb +16 -0
  40. data/lib/active_support/core_ext/time/conversions.rb +0 -2
  41. data/lib/active_support/core_ext/time/zones.rb +1 -1
  42. data/lib/active_support/core_ext.rb +0 -1
  43. data/lib/active_support/current_attributes.rb +38 -40
  44. data/lib/active_support/delegation.rb +202 -0
  45. data/lib/active_support/dependencies/autoload.rb +0 -12
  46. data/lib/active_support/deprecation/constant_accessor.rb +47 -26
  47. data/lib/active_support/deprecation/proxy_wrappers.rb +9 -12
  48. data/lib/active_support/deprecation/reporting.rb +9 -4
  49. data/lib/active_support/deprecation.rb +8 -5
  50. data/lib/active_support/descendants_tracker.rb +9 -87
  51. data/lib/active_support/duration/iso8601_parser.rb +2 -2
  52. data/lib/active_support/duration/iso8601_serializer.rb +1 -2
  53. data/lib/active_support/duration.rb +11 -6
  54. data/lib/active_support/encrypted_file.rb +1 -1
  55. data/lib/active_support/error_reporter.rb +41 -3
  56. data/lib/active_support/evented_file_update_checker.rb +0 -1
  57. data/lib/active_support/execution_wrapper.rb +0 -1
  58. data/lib/active_support/file_update_checker.rb +1 -1
  59. data/lib/active_support/fork_tracker.rb +2 -38
  60. data/lib/active_support/gem_version.rb +2 -2
  61. data/lib/active_support/hash_with_indifferent_access.rb +6 -8
  62. data/lib/active_support/html_safe_translation.rb +3 -0
  63. data/lib/active_support/log_subscriber.rb +0 -12
  64. data/lib/active_support/logger.rb +15 -2
  65. data/lib/active_support/logger_thread_safe_level.rb +0 -8
  66. data/lib/active_support/message_pack/extensions.rb +15 -2
  67. data/lib/active_support/message_verifier.rb +12 -0
  68. data/lib/active_support/multibyte/chars.rb +2 -2
  69. data/lib/active_support/notifications/fanout.rb +4 -7
  70. data/lib/active_support/notifications/instrumenter.rb +21 -18
  71. data/lib/active_support/notifications.rb +28 -27
  72. data/lib/active_support/number_helper/number_converter.rb +2 -2
  73. data/lib/active_support/option_merger.rb +2 -2
  74. data/lib/active_support/ordered_options.rb +53 -15
  75. data/lib/active_support/proxy_object.rb +8 -5
  76. data/lib/active_support/railtie.rb +4 -11
  77. data/lib/active_support/string_inquirer.rb +1 -1
  78. data/lib/active_support/subscriber.rb +1 -0
  79. data/lib/active_support/tagged_logging.rb +0 -1
  80. data/lib/active_support/test_case.rb +3 -1
  81. data/lib/active_support/testing/assertions.rb +4 -4
  82. data/lib/active_support/testing/constant_stubbing.rb +30 -8
  83. data/lib/active_support/testing/deprecation.rb +5 -12
  84. data/lib/active_support/testing/isolation.rb +20 -8
  85. data/lib/active_support/testing/method_call_assertions.rb +2 -16
  86. data/lib/active_support/testing/parallelization/server.rb +3 -0
  87. data/lib/active_support/testing/strict_warnings.rb +8 -4
  88. data/lib/active_support/testing/tests_without_assertions.rb +19 -0
  89. data/lib/active_support/testing/time_helpers.rb +3 -3
  90. data/lib/active_support/time_with_zone.rb +8 -4
  91. data/lib/active_support/values/time_zone.rb +7 -7
  92. data/lib/active_support/xml_mini.rb +11 -2
  93. data/lib/active_support.rb +2 -1
  94. metadata +49 -15
  95. data/lib/active_support/deprecation/instance_delegator.rb +0 -65
  96. data/lib/active_support/ruby_features.rb +0 -7
@@ -109,7 +109,7 @@ class String
109
109
  when omission.bytesize == truncate_to
110
110
  omission.dup
111
111
  else
112
- self.class.new.tap do |cut|
112
+ self.class.new.force_encoding(encoding).tap do |cut|
113
113
  cut_at = truncate_to - omission.bytesize
114
114
 
115
115
  each_grapheme_cluster do |grapheme|
@@ -19,7 +19,7 @@ class String
19
19
  # >> "lj".upcase
20
20
  # => "LJ"
21
21
  #
22
- # == Method chaining
22
+ # == \Method chaining
23
23
  #
24
24
  # All the methods on the Chars proxy which normally return a string will return a Chars object. This allows
25
25
  # method chaining on the result of any of these methods.
@@ -77,13 +77,6 @@ module ActiveSupport # :nodoc:
77
77
  @html_safe = other.html_safe?
78
78
  end
79
79
 
80
- def clone_empty # :nodoc:
81
- ActiveSupport.deprecator.warn <<~EOM
82
- ActiveSupport::SafeBuffer#clone_empty is deprecated and will be removed in Rails 7.2.
83
- EOM
84
- self[0, 0]
85
- end
86
-
87
80
  def concat(value)
88
81
  unless value.nil?
89
82
  super(implicit_html_escape_interpolated_argument(value))
@@ -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,7 +144,7 @@ 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
 
165
150
  # When there are two occurrences of a nominal time due to DST ending,
@@ -334,7 +319,12 @@ class Time
334
319
  if other.class == Time
335
320
  compare_without_coercion(other)
336
321
  elsif other.is_a?(Time)
337
- compare_without_coercion(other.to_time)
322
+ # also avoid ActiveSupport::TimeWithZone#to_time before Rails 8.0
323
+ if other.respond_to?(:comparable_time)
324
+ compare_without_coercion(other.comparable_time)
325
+ else
326
+ compare_without_coercion(other.to_time)
327
+ end
338
328
  else
339
329
  to_datetime <=> other
340
330
  end
@@ -13,4 +13,20 @@ class Time
13
13
  def to_time
14
14
  preserve_timezone ? self : getlocal
15
15
  end
16
+
17
+ def preserve_timezone # :nodoc:
18
+ active_support_local_zone == zone || super
19
+ end
20
+
21
+ private
22
+ @@active_support_local_tz = nil
23
+
24
+ def active_support_local_zone
25
+ @@active_support_local_zone = nil if @@active_support_local_tz != ENV["TZ"]
26
+ @@active_support_local_zone ||=
27
+ begin
28
+ @@active_support_local_tz = ENV["TZ"]
29
+ Time.new.zone
30
+ end
31
+ end
16
32
  end
@@ -58,8 +58,6 @@ class Time
58
58
  end
59
59
  end
60
60
  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
61
 
64
62
  # Returns a formatted string of the offset from UTC, or an alternative
65
63
  # 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,202 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "set"
4
+
5
+ module ActiveSupport
6
+ # Error generated by +delegate+ when a method is called on +nil+ and +allow_nil+
7
+ # option is not used.
8
+ class DelegationError < NoMethodError
9
+ class << self
10
+ def nil_target(method_name, target) # :nodoc:
11
+ new("#{method_name} delegated to #{target}, but #{target} is nil")
12
+ end
13
+ end
14
+ end
15
+
16
+ module Delegation # :nodoc:
17
+ RUBY_RESERVED_KEYWORDS = %w(__ENCODING__ __LINE__ __FILE__ alias and BEGIN begin break
18
+ case class def defined? do else elsif END end ensure false for if in module next nil
19
+ not or redo rescue retry return self super then true undef unless until when while yield)
20
+ RESERVED_METHOD_NAMES = (RUBY_RESERVED_KEYWORDS + %w(_ arg args block)).to_set.freeze
21
+
22
+ class << self
23
+ def generate(owner, methods, location: nil, to: nil, prefix: nil, allow_nil: nil, nilable: true, private: nil, as: nil, signature: nil)
24
+ unless to
25
+ raise ArgumentError, "Delegation needs a target. Supply a keyword argument 'to' (e.g. delegate :hello, to: :greeter)."
26
+ end
27
+
28
+ if prefix == true && /^[^a-z_]/.match?(to)
29
+ raise ArgumentError, "Can only automatically set the delegation prefix when delegating to a method."
30
+ end
31
+
32
+ method_prefix = \
33
+ if prefix
34
+ "#{prefix == true ? to : prefix}_"
35
+ else
36
+ ""
37
+ end
38
+
39
+ location ||= caller_locations(1, 1).first
40
+ file, line = location.path, location.lineno
41
+
42
+ receiver = if to.is_a?(Module)
43
+ if to.name.nil?
44
+ raise ArgumentError, "Can't delegate to anonymous class or module: #{to}"
45
+ end
46
+
47
+ unless Inflector.safe_constantize(to.name).equal?(to)
48
+ raise ArgumentError, "Can't delegate to detached class or module: #{to.name}"
49
+ end
50
+
51
+ "::#{to.name}"
52
+ else
53
+ to.to_s
54
+ end
55
+ receiver = "self.#{receiver}" if RESERVED_METHOD_NAMES.include?(receiver)
56
+
57
+ explicit_receiver = false
58
+ receiver_class = if as
59
+ explicit_receiver = true
60
+ as
61
+ elsif to.is_a?(Module)
62
+ to.singleton_class
63
+ elsif receiver == "self.class"
64
+ nilable = false # self.class can't possibly be nil
65
+ owner.singleton_class
66
+ end
67
+
68
+ method_def = []
69
+ method_names = []
70
+
71
+ method_def << "self.private" if private
72
+
73
+ methods.each do |method|
74
+ method_name = prefix ? "#{method_prefix}#{method}" : method
75
+ method_names << method_name.to_sym
76
+
77
+ # Attribute writer methods only accept one argument. Makes sure []=
78
+ # methods still accept two arguments.
79
+ definition = \
80
+ if signature
81
+ signature
82
+ elsif /[^\]]=\z/.match?(method)
83
+ "arg"
84
+ else
85
+ method_object = if receiver_class
86
+ begin
87
+ receiver_class.public_instance_method(method)
88
+ rescue NameError
89
+ raise if explicit_receiver
90
+ # Do nothing. Fall back to `"..."`
91
+ end
92
+ end
93
+
94
+ if method_object
95
+ parameters = method_object.parameters
96
+
97
+ if parameters.map(&:first).intersect?([:opt, :rest, :keyreq, :key, :keyrest])
98
+ "..."
99
+ else
100
+ defn = parameters.filter_map { |type, arg| arg if type == :req }
101
+ defn << "&"
102
+ defn.join(", ")
103
+ end
104
+ else
105
+ "..."
106
+ end
107
+ end
108
+
109
+ # The following generated method calls the target exactly once, storing
110
+ # the returned value in a dummy variable.
111
+ #
112
+ # Reason is twofold: On one hand doing less calls is in general better.
113
+ # On the other hand it could be that the target has side-effects,
114
+ # whereas conceptually, from the user point of view, the delegator should
115
+ # be doing one call.
116
+ if nilable == false
117
+ method_def <<
118
+ "def #{method_name}(#{definition})" <<
119
+ " (#{receiver}).#{method}(#{definition})" <<
120
+ "end"
121
+ elsif allow_nil
122
+ method = method.to_s
123
+
124
+ method_def <<
125
+ "def #{method_name}(#{definition})" <<
126
+ " _ = #{receiver}" <<
127
+ " if !_.nil? || nil.respond_to?(:#{method})" <<
128
+ " _.#{method}(#{definition})" <<
129
+ " end" <<
130
+ "end"
131
+ else
132
+ method = method.to_s
133
+ method_name = method_name.to_s
134
+
135
+ method_def <<
136
+ "def #{method_name}(#{definition})" <<
137
+ " _ = #{receiver}" <<
138
+ " _.#{method}(#{definition})" <<
139
+ "rescue NoMethodError => e" <<
140
+ " if _.nil? && e.name == :#{method}" <<
141
+ " raise ::ActiveSupport::DelegationError.nil_target(:#{method_name}, :'#{receiver}')" <<
142
+ " else" <<
143
+ " raise" <<
144
+ " end" <<
145
+ "end"
146
+ end
147
+ end
148
+ owner.module_eval(method_def.join(";"), file, line)
149
+ method_names
150
+ end
151
+
152
+ def generate_method_missing(owner, target, allow_nil: nil)
153
+ target = target.to_s
154
+ target = "self.#{target}" if RESERVED_METHOD_NAMES.include?(target) || target == "__target"
155
+
156
+ if allow_nil
157
+ owner.module_eval <<~RUBY, __FILE__, __LINE__ + 1
158
+ def respond_to_missing?(name, include_private = false)
159
+ # It may look like an oversight, but we deliberately do not pass
160
+ # +include_private+, because they do not get delegated.
161
+
162
+ return false if name == :marshal_dump || name == :_dump
163
+ #{target}.respond_to?(name) || super
164
+ end
165
+
166
+ def method_missing(method, ...)
167
+ __target = #{target}
168
+ if __target.nil? && !nil.respond_to?(method)
169
+ nil
170
+ elsif __target.respond_to?(method)
171
+ __target.public_send(method, ...)
172
+ else
173
+ super
174
+ end
175
+ end
176
+ RUBY
177
+ else
178
+ owner.module_eval <<~RUBY, __FILE__, __LINE__ + 1
179
+ def respond_to_missing?(name, include_private = false)
180
+ # It may look like an oversight, but we deliberately do not pass
181
+ # +include_private+, because they do not get delegated.
182
+
183
+ return false if name == :marshal_dump || name == :_dump
184
+ #{target}.respond_to?(name) || super
185
+ end
186
+
187
+ def method_missing(method, ...)
188
+ __target = #{target}
189
+ if __target.nil? && !nil.respond_to?(method)
190
+ raise ::ActiveSupport::DelegationError.nil_target(method, :'#{target}')
191
+ elsif __target.respond_to?(method)
192
+ __target.public_send(method, ...)
193
+ else
194
+ super
195
+ end
196
+ end
197
+ RUBY
198
+ end
199
+ end
200
+ end
201
+ end
202
+ 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("::")
@@ -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
@@ -3,7 +3,7 @@
3
3
  module ActiveSupport
4
4
  class Deprecation
5
5
  class DeprecationProxy # :nodoc:
6
- def self.new(*args, &block)
6
+ def self.new(*args, **kwargs, &block)
7
7
  object = args.first
8
8
 
9
9
  return object unless object
@@ -36,11 +36,10 @@ module ActiveSupport
36
36
  # (Backtrace)
37
37
  # # => "#<Object:0x007fb9b34c34b0>"
38
38
  class DeprecatedObjectProxy < DeprecationProxy
39
- def initialize(object, message, deprecator = nil)
39
+ def initialize(object, message, deprecator)
40
40
  @object = object
41
41
  @message = message
42
- ActiveSupport.deprecator.warn("DeprecatedObjectProxy without a deprecator is deprecated") unless deprecator
43
- @deprecator = deprecator || ActiveSupport::Deprecation._instance
42
+ @deprecator = deprecator
44
43
  end
45
44
 
46
45
  private
@@ -86,12 +85,11 @@ module ActiveSupport
86
85
  # example.request.to_s
87
86
  # # => "special_request"
88
87
  class DeprecatedInstanceVariableProxy < DeprecationProxy
89
- def initialize(instance, method, var = "@#{method}", deprecator = nil)
88
+ def initialize(instance, method, var = "@#{method}", deprecator:)
90
89
  @instance = instance
91
90
  @method = method
92
91
  @var = var
93
- ActiveSupport.deprecator.warn("DeprecatedInstanceVariableProxy without a deprecator is deprecated") unless deprecator
94
- @deprecator = deprecator || ActiveSupport::Deprecation._instance
92
+ @deprecator = deprecator
95
93
  end
96
94
 
97
95
  private
@@ -127,13 +125,12 @@ module ActiveSupport
127
125
  super
128
126
  end
129
127
 
130
- def initialize(old_const, new_const, deprecator = nil, message: "#{old_const} is deprecated! Use #{new_const} instead.")
128
+ def initialize(old_const, new_const, deprecator, message: "#{old_const} is deprecated! Use #{new_const} instead.")
131
129
  Kernel.require "active_support/inflector/methods"
132
130
 
133
131
  @old_const = old_const
134
132
  @new_const = new_const
135
- ActiveSupport.deprecator.warn("DeprecatedConstantProxy without a deprecator is deprecated") unless deprecator
136
- @deprecator = deprecator || ActiveSupport::Deprecation._instance
133
+ @deprecator = deprecator
137
134
  @message = message
138
135
  end
139
136
 
@@ -183,9 +180,9 @@ module ActiveSupport
183
180
  target.const_get(name)
184
181
  end
185
182
 
186
- def method_missing(called, *args, &block)
183
+ def method_missing(...)
187
184
  @deprecator.warn(@message, caller_locations)
188
- target.__send__(called, *args, &block)
185
+ target.__send__(...)
189
186
  end
190
187
  end
191
188
  end