Merge pull request #41012 from kamipo/fix_complicated_through_association

Fix complicated has_many through with nested where condition
This commit is contained in:
Ryuta Kamizono 2021-01-05 15:56:41 +09:00 committed by GitHub
commit 21b8d9b08c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 39 additions and 12 deletions

View File

@ -131,11 +131,14 @@ module ActiveRecord
if scope_chain_item == chain_head.scope
scope.merge! item.except(:where, :includes, :unscope, :order)
elsif !item.references_values.empty?
join_dependency = item.construct_join_dependency(
item.eager_load_values | item.includes_values, Arel::Nodes::OuterJoin
)
scope.joins!(*item.joins_values, join_dependency)
scope.left_outer_joins!(*item.left_outer_joins_values)
scope.joins_values |= item.joins_values
scope.left_outer_joins_values |= item.left_outer_joins_values
associations = item.eager_load_values | item.includes_values
unless associations.empty?
scope.joins_values << item.construct_join_dependency(associations, Arel::Nodes::OuterJoin)
end
end
reflection.all_includes do

View File

@ -1140,7 +1140,7 @@ module ActiveRecord
self.references_values |= references unless references.empty?
parts = predicate_builder.build_from_hash(opts) do |table_name|
lookup_reflection_from_join_dependencies(table_name)
lookup_table_klass_from_join_dependencies(table_name)
end
when Arel::Nodes::Node
parts = [opts]
@ -1153,9 +1153,9 @@ module ActiveRecord
alias :build_having_clause :build_where_clause
private
def lookup_reflection_from_join_dependencies(table_name)
def lookup_table_klass_from_join_dependencies(table_name)
each_join_dependencies do |join|
return join.reflection if table_name == join.table_name
return join.base_klass if table_name == join.table_name
end
nil
end
@ -1366,7 +1366,7 @@ module ActiveRecord
elsif field.match?(/\A\w+\.\w+\z/)
table, column = field.split(".")
predicate_builder.resolve_arel_attribute(table, column) do
lookup_reflection_from_join_dependencies(table)
lookup_table_klass_from_join_dependencies(table)
end
else
yield field

View File

@ -33,10 +33,13 @@ module ActiveRecord
return self
end
reflection ||= yield table_name if block_given?
if reflection
association_klass = reflection.klass unless reflection.polymorphic?
elsif block_given?
association_klass = yield table_name
end
if reflection && !reflection.polymorphic?
association_klass = reflection.klass
if association_klass
arel_table = association_klass.arel_table
arel_table = arel_table.alias(table_name) if arel_table.name != table_name
TableMetadata.new(association_klass, arel_table, reflection)

View File

@ -66,6 +66,14 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
assert_equal [comments(:eager_other_comment1)], authors(:mary).comments.merge(Post.left_joins(:comments))
end
def test_through_association_with_through_scope_and_nested_where
company = Company.create!(name: "special")
developer = SpecialDeveloper.create!
SpecialContract.create!(company: company, special_developer: developer)
assert_equal [developer], company.special_developers.where.not("contracts.id": nil)
end
def test_preload_with_nested_association
posts = Post.where(id: [authors(:david).id, authors(:mary).id]).
preload(:author, :author_favorites_with_scope).order(:id).to_a

View File

@ -13,6 +13,8 @@ class Company < AbstractCompany
has_one :dummy_account, foreign_key: "firm_id", class_name: "Account"
has_many :contracts
has_many :developers, through: :contracts
has_many :special_contracts, -> { includes(:special_developer).where.not("developers.id": nil) }
has_many :special_developers, through: :special_contracts
alias_attribute :new_name, :name
attribute :metadata, :json

View File

@ -30,3 +30,9 @@ end
class NewContract < Contract
validates :company_id, presence: true
end
class SpecialContract < ActiveRecord::Base
self.table_name = "contracts"
belongs_to :company
belongs_to :special_developer, foreign_key: "developer_id"
end

View File

@ -114,6 +114,11 @@ end
class SubDeveloper < Developer
end
class SpecialDeveloper < ActiveRecord::Base
self.table_name = "developers"
has_many :special_contracts, foreign_key: "developer_id"
end
class SymbolIgnoredDeveloper < ActiveRecord::Base
self.table_name = "developers"
self.ignored_columns = [:first_name, :last_name]