From 329260cc0fdfc980802c6bddbc5853edf1bf6a53 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20Mendon=C3=A7a=20Fran=C3=A7a?= Date: Wed, 22 May 2024 19:46:14 -0400 Subject: [PATCH] Merge pull request #51721 from joshuay03/fix-same-association-name-as-demodularized-model-name [Fix #51720] Infer association klass as top level if model has same demodularized name --- activerecord/CHANGELOG.md | 14 ++++++++++ activerecord/lib/active_record/reflection.rb | 8 ++++-- activerecord/test/cases/reflection_test.rb | 27 ++++++++++++++++++++ activerecord/test/models/user.rb | 6 +++++ 4 files changed, 53 insertions(+), 2 deletions(-) diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index 548a3793adf76..0beeaf5f285b8 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -1,3 +1,17 @@ +* Fix inference of association model on nested models with the same demodularized name. + + E.g. with the following setup: + + ```ruby + class Nested::Post < ApplicationRecord + has_one :post, through: :other + end + ``` + + Before, `#post` would infer the model as `Nested::Post`, but now it correctly infers `Post`. + + *Joshua Young* + * PostgreSQL `Cidr#change?` detects the address prefix change. *Taketo Takashima* diff --git a/activerecord/lib/active_record/reflection.rb b/activerecord/lib/active_record/reflection.rb index 496727ebd506f..a8e7e7bcffb88 100644 --- a/activerecord/lib/active_record/reflection.rb +++ b/activerecord/lib/active_record/reflection.rb @@ -428,13 +428,17 @@ def autosave=(autosave) # a new association object. Use +build_association+ or +create_association+ # instead. This allows plugins to hook into association object creation. def klass - @klass ||= compute_class(class_name) + @klass ||= compute_class(compute_name(class_name)) end def compute_class(name) name.constantize end + def compute_name(name) # :nodoc: + active_record.name.demodulize == name ? "::#{name}" : name + end + # Returns +true+ if +self+ and +other_aggregation+ have the same +name+ attribute, +active_record+ attribute, # and +other_aggregation+ has an options hash assigned to it. def ==(other_aggregation) @@ -994,7 +998,7 @@ def through_reflection? end def klass - @klass ||= delegate_reflection.compute_class(class_name) + @klass ||= delegate_reflection.compute_class(compute_name(class_name)) end # Returns the source of the through reflection. It checks both a singularized diff --git a/activerecord/test/cases/reflection_test.rb b/activerecord/test/cases/reflection_test.rb index 60fcd20b77798..64bd852833bb7 100644 --- a/activerecord/test/cases/reflection_test.rb +++ b/activerecord/test/cases/reflection_test.rb @@ -30,6 +30,9 @@ require "models/user_with_invalid_relation" require "models/hardback" require "models/sharded/comment" +require "models/admin" +require "models/admin/user" +require "models/user" class ReflectionTest < ActiveRecord::TestCase include ActiveRecord::Reflection @@ -181,6 +184,30 @@ def test_reflection_klass_requires_ar_subclass end end + def test_reflection_klass_with_same_demodularized_name + reflection = ActiveRecord::Reflection.create( + :has_one, + :user, + nil, + {}, + Admin::User + ) + + assert_equal User, reflection.klass + end + + def test_reflection_klass_with_same_demodularized_different_modularized_name + reflection = ActiveRecord::Reflection.create( + :has_one, + :user, + nil, + { class_name: "Nested::User" }, + Admin::User + ) + + assert_equal Nested::User, reflection.klass + end + def test_aggregation_reflection reflection_for_address = AggregateReflection.new( :address, nil, { mapping: [ %w(address_street street), %w(address_city city), %w(address_country country) ] }, Customer diff --git a/activerecord/test/models/user.rb b/activerecord/test/models/user.rb index a2f90c6968310..21cb06477330c 100644 --- a/activerecord/test/models/user.rb +++ b/activerecord/test/models/user.rb @@ -23,3 +23,9 @@ class User < ActiveRecord::Base class UserWithNotification < User after_create -> { Notification.create! message: "A new user has been created." } end + +module Nested + class User < ActiveRecord::Base + self.table_name = "users" + end +end