This project is archived and is in readonly mode.

#6685 new
Ryan Aipperspach

Move conditions for polymorphic joins from WHERE to ON clause

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

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

No comments found

Create your profile

Help contribute to this project by taking a few moments to create your personal profile. Create your profile »

<h2 style="font-size: 14px">Tickets have moved to Github</h2>

The new ticket tracker is available at <a href="https://github.com/rails/rails/issues">https://github.com/rails/rails/issues</a>

People watching this ticket

Attachments

Pages