Lighthouse has a new layout. Prefer the old one? Return to the old layout, and switch back any time from the link at the top of each page.

This project is archived and is in readonly mode.

Move conditions for polymorphic joins from WHERE to ON clause

#6685

The problem

Polymorphic joins currently add a condition (e.g., taggable_type="Post") to the WHERE clause. When the table being joined is in an outer join (e.g., via a Tag.include(:tagged_posts)), this precludes finding objects that have no matching objects across the join (e.g., Tags that don't have any Posts).

To reproduce, see test_has_many_polymporphic_with_source_type_and_some_empty_children in join_model_test.rb.

In the current code, the query:

Tag.includes(:tagged_posts).where(:taggings => {:id => nil}).limit(100)

generates SQL like:

SELECT ... FROM tags LEFT OUTER JOIN "taggings" ON "taggings"."tag_id" = "tags"."id" LEFT OUTER JOIN "posts" ON "posts"."id" = "taggings"."taggable_id" WHERE "taggings"."taggable_type" = 'Post' AND "taggings"."id" IS NULL LIMIT 100

Which will never find any results because of the conflicting conditions "taggings"."taggable_type" = 'Post' and "taggings"."id" IS NULL.

After applying this patch, it instead generates:

SELECT ... FROM "tags" LEFT OUTER JOIN "taggings" ON "taggings"."tag_id" = "tags"."id" LEFT OUTER JOIN "posts" ON "posts"."id" = "taggings"."taggable_id" AND "taggings"."taggable_type" = 'Post' WHERE "taggings"."id" IS NULL LIMIT 100

Solution

ThroughReflection#conditions currently applies a condition of foreign_type => options[:source_type] whenever an association provides a source_type (either directly, or implicitly via a :through or :source option, which is the source of the errant condition in the query above.

This was replaced with a constraint that is inserted when a join is created, in both AssociationScope#add_constraints and JoinAssociation#join_to (with the shared code for generating the constraint in join_helper.rb:

def append_source_type_condition(constraint, table, foreign_table, reflection)
  source_type = reflection.options[:source_type] ||
    (reflection.source_reflection && 
     reflection.source_reflection.options[:source_type])
     
  if (source_type)
    table.create_and([
      constraint,
      foreign_table[reflection.foreign_type].eq(source_type)
    ])
  else
    constraint
  end
end

Reported by Ryan Aipperspach · April 7th, 2011 @ 11:02 PM

State: new
Milestone: none
Assigned to: nobody
Importance: none

Activity

No comments yet.