Fix alias_attribute to ignore methods defined in parent classes

Fix: https://github.com/rails/rails/issues/52144

When defining regular attributes, inherited methods aren't overriden,
however when defining aliased attributes, inherited methods aren't
considered.

This behavior could be debatted, but that was the behavior prior
to https://github.com/rails/rails/pull/52118, so I'm restoring it.
This commit is contained in:
Jean Boussier 2024-06-18 09:37:16 +02:00
parent 98636b3a78
commit 403743ed88
3 changed files with 43 additions and 17 deletions

@ -312,26 +312,33 @@ def define_attribute_method(attr_name, _owner: generated_attribute_methods, as:
end
end
def define_attribute_method_pattern(pattern, attr_name, owner:, as:) # :nodoc:
def define_attribute_method_pattern(pattern, attr_name, owner:, as:, override: false) # :nodoc:
canonical_method_name = pattern.method_name(attr_name)
public_method_name = pattern.method_name(as)
unless instance_method_already_implemented?(public_method_name)
generate_method = "define_method_#{pattern.proxy_target}"
# If defining a regular attribute method, we don't override methods that are explictly
# defined in parrent classes.
if instance_method_already_implemented?(public_method_name)
# However, for `alias_attribute`, we always define the method.
# We check for override second because `instance_method_already_implemented?`
# also check for dangerous methods.
return unless override
end
if respond_to?(generate_method, true)
send(generate_method, attr_name.to_s, owner: owner, as: as)
else
define_proxy_call(
owner,
canonical_method_name,
pattern.proxy_target,
pattern.parameters,
attr_name.to_s,
namespace: :active_model_proxy,
as: public_method_name
)
end
generate_method = "define_method_#{pattern.proxy_target}"
if respond_to?(generate_method, true)
send(generate_method, attr_name.to_s, owner: owner, as: as)
else
define_proxy_call(
owner,
canonical_method_name,
pattern.proxy_target,
pattern.parameters,
attr_name.to_s,
namespace: :active_model_proxy,
as: public_method_name
)
end
end

@ -91,7 +91,7 @@ def alias_attribute_method_definition(code_generator, pattern, new_name, old_nam
raise ArgumentError, "#{self.name} model aliases `#{old_name}`, but `#{old_name}` is not an attribute. " \
"Use `alias_method :#{new_name}, :#{old_name}` or define the method manually."
else
define_attribute_method_pattern(pattern, old_name, owner: code_generator, as: new_name)
define_attribute_method_pattern(pattern, old_name, owner: code_generator, as: new_name, override: true)
end
end

@ -1217,6 +1217,25 @@ def some_method_that_is_not_on_super
alias_attribute :subject, :title
end
test "#alias_attribute override methods defined in parent models" do
parent_model = Class.new(ActiveRecord::Base) do
self.abstract_class = true
def subject
"Abstract Subject"
end
end
subclass = Class.new(parent_model) do
self.table_name = "topics"
alias_attribute :subject, :title
end
obj = subclass.new
obj.title = "hey"
assert_equal("hey", obj.subject)
end
test "aliases to the same attribute name do not conflict with each other" do
first_model_object = ToBeLoadedFirst.new(author_name: "author 1")
assert_equal("author 1", first_model_object.subject)