1
0
Fork 0
mirror of https://github.com/rails/rails.git synced 2022-11-09 12:12:34 -05:00

has_many :through with unscope should affect to through scope

The order of scope evaluation should be from through scope to the
association's own scope. Otherwise the association's scope cannot affect
to through scope.

Fixes #13677.
Closes #28449.
This commit is contained in:
Ryuta Kamizono 2017-09-07 07:33:38 +09:00
parent 0a94612a97
commit 3acc5d6ef7
4 changed files with 19 additions and 23 deletions

View file

@ -24,10 +24,10 @@ module ActiveRecord
scope = klass.unscoped
owner = association.owner
alias_tracker = AliasTracker.create(klass.connection, klass.table_name)
chain_head, chain_tail = get_chain(reflection, association, alias_tracker)
chain = get_chain(reflection, association, alias_tracker)
scope.extending! reflection.extensions
add_constraints(scope, owner, chain_head, chain_tail)
add_constraints(scope, owner, chain)
end
def join_type
@ -98,7 +98,6 @@ module ActiveRecord
end
class ReflectionProxy < SimpleDelegator # :nodoc:
attr_accessor :next
attr_reader :alias_name
def initialize(reflection, alias_name)
@ -111,36 +110,32 @@ module ActiveRecord
def get_chain(reflection, association, tracker)
name = reflection.name
runtime_reflection = Reflection::RuntimeReflection.new(reflection, association)
previous_reflection = runtime_reflection
chain = [Reflection::RuntimeReflection.new(reflection, association)]
reflection.chain.drop(1).each do |refl|
alias_name = tracker.aliased_table_for(
refl.table_name,
refl.alias_candidate(name),
refl.klass.type_caster
)
proxy = ReflectionProxy.new(refl, alias_name)
previous_reflection.next = proxy
previous_reflection = proxy
chain << ReflectionProxy.new(refl, alias_name)
end
[runtime_reflection, previous_reflection]
chain
end
def add_constraints(scope, owner, chain_head, chain_tail)
owner_reflection = chain_tail
def add_constraints(scope, owner, chain)
owner_reflection = chain.last
table = owner_reflection.alias_name
scope = last_chain_scope(scope, table, owner_reflection, owner)
reflection = chain_head
while reflection
chain.each_cons(2) do |reflection, next_reflection|
table = reflection.alias_name
next_reflection = reflection.next
unless reflection == chain_tail
foreign_table = next_reflection.alias_name
scope = next_chain_scope(scope, table, reflection, foreign_table, next_reflection)
end
foreign_table = next_reflection.alias_name
scope = next_chain_scope(scope, table, reflection, foreign_table, next_reflection)
end
chain_head = chain.first
chain.reverse_each do |reflection|
table = reflection.alias_name
# Exclude the scope of the association itself, because that
# was already merged in the #scope method.
reflection.constraints.each do |scope_chain_item|
@ -158,8 +153,6 @@ module ActiveRecord
scope.where_clause += item.where_clause
scope.order_values |= item.order_values
end
reflection = next_reflection
end
scope

View file

@ -1077,8 +1077,6 @@ module ActiveRecord
end
class RuntimeReflection < PolymorphicReflection # :nodoc:
attr_accessor :next
def initialize(reflection, association)
@reflection = reflection
@association = association

View file

@ -1250,6 +1250,10 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
TenantMembership.current_member = nil
end
def test_has_many_through_with_unscope_should_affect_to_through_scope
assert_equal [comments(:eager_other_comment1)], authors(:mary).unordered_comments
end
def test_has_many_through_with_scope_should_respect_table_alias
family = Family.create!
users = 3.times.map { User.create! }

View file

@ -40,6 +40,7 @@ class Author < ActiveRecord::Base
class_name: "Post"
has_many :comments_desc, -> { order("comments.id DESC") }, through: :posts, source: :comments
has_many :unordered_comments, -> { unscope(:order).distinct }, through: :posts_sorted_by_id_limited, source: :comments
has_many :funky_comments, through: :posts, source: :comments
has_many :ordered_uniq_comments, -> { distinct.order("comments.id") }, through: :posts, source: :comments
has_many :ordered_uniq_comments_desc, -> { distinct.order("comments.id DESC") }, through: :posts, source: :comments