Skip to content

Commit cacb847

Browse files
authored
Merge pull request rails#51721 from joshuay03/fix-same-association-name-as-demodularized-model-name
[Fix rails#51720] Infer association klass as top level if model has same demodularized name
2 parents 6a283ac + 0516eaf commit cacb847

File tree

4 files changed

+53
-2
lines changed

4 files changed

+53
-2
lines changed

activerecord/CHANGELOG.md

+14
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,17 @@
1+
* Fix inference of association model on nested models with the same demodularized name.
2+
3+
E.g. with the following setup:
4+
5+
```ruby
6+
class Nested::Post < ApplicationRecord
7+
has_one :post, through: :other
8+
end
9+
```
10+
11+
Before, `#post` would infer the model as `Nested::Post`, but now it correctly infers `Post`.
12+
13+
*Joshua Young*
14+
115
* Add public method for checking if a table is ignored by the schema cache.
216

317
Previously, an application would need to reimplement `ignored_table?` from the schema cache class to check if a table was set to be ignored. This adds a public method to support this and updates the schema cache to use that directly.

activerecord/lib/active_record/reflection.rb

+6-2
Original file line numberDiff line numberDiff line change
@@ -428,13 +428,17 @@ def autosave=(autosave)
428428
# a new association object. Use +build_association+ or +create_association+
429429
# instead. This allows plugins to hook into association object creation.
430430
def klass
431-
@klass ||= compute_class(class_name)
431+
@klass ||= compute_class(compute_name(class_name))
432432
end
433433

434434
def compute_class(name)
435435
name.constantize
436436
end
437437

438+
def compute_name(name) # :nodoc:
439+
active_record.name.demodulize == name ? "::#{name}" : name
440+
end
441+
438442
# Returns +true+ if +self+ and +other_aggregation+ have the same +name+ attribute, +active_record+ attribute,
439443
# and +other_aggregation+ has an options hash assigned to it.
440444
def ==(other_aggregation)
@@ -994,7 +998,7 @@ def through_reflection?
994998
end
995999

9961000
def klass
997-
@klass ||= delegate_reflection.compute_class(class_name)
1001+
@klass ||= delegate_reflection.compute_class(compute_name(class_name))
9981002
end
9991003

10001004
# Returns the source of the through reflection. It checks both a singularized

activerecord/test/cases/reflection_test.rb

+27
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,9 @@
3030
require "models/user_with_invalid_relation"
3131
require "models/hardback"
3232
require "models/sharded/comment"
33+
require "models/admin"
34+
require "models/admin/user"
35+
require "models/user"
3336

3437
class ReflectionTest < ActiveRecord::TestCase
3538
include ActiveRecord::Reflection
@@ -181,6 +184,30 @@ def test_reflection_klass_requires_ar_subclass
181184
end
182185
end
183186

187+
def test_reflection_klass_with_same_demodularized_name
188+
reflection = ActiveRecord::Reflection.create(
189+
:has_one,
190+
:user,
191+
nil,
192+
{},
193+
Admin::User
194+
)
195+
196+
assert_equal User, reflection.klass
197+
end
198+
199+
def test_reflection_klass_with_same_demodularized_different_modularized_name
200+
reflection = ActiveRecord::Reflection.create(
201+
:has_one,
202+
:user,
203+
nil,
204+
{ class_name: "Nested::User" },
205+
Admin::User
206+
)
207+
208+
assert_equal Nested::User, reflection.klass
209+
end
210+
184211
def test_aggregation_reflection
185212
reflection_for_address = AggregateReflection.new(
186213
:address, nil, { mapping: [ %w(address_street street), %w(address_city city), %w(address_country country) ] }, Customer

activerecord/test/models/user.rb

+6
Original file line numberDiff line numberDiff line change
@@ -23,3 +23,9 @@ class User < ActiveRecord::Base
2323
class UserWithNotification < User
2424
after_create -> { Notification.create! message: "A new user has been created." }
2525
end
26+
27+
module Nested
28+
class User < ActiveRecord::Base
29+
self.table_name = "users"
30+
end
31+
end

0 commit comments

Comments
 (0)