rails: has_many through with scope block, STI and polymorphic associations that worked in 5.0 breaks in 6.0+

We have a has_many …, through: that also involves a scope block, STI and polymorphic associations (bingo!).

Joining on this association worked fine in Rails 5.0.7.2, but broke when updating to 5.1.7. We’ve checked and it also fails on 5.2.4.3 and 6.0.3.2.

Steps to reproduce

UPDATE: See reproduction script in https://github.com/rails/rails/issues/40109#issuecomment-680799482 instead.

Set up associations similar to

class Item < ApplicationRecord
  has_many :invoice_lines, as: :invoicable

  has_many :item_invoices,
    -> { where("buyer_invoices.type" => ItemInvoice.name).distinct },  # STI stores leaf class.
    through: :invoice_lines,
    source: :invoice,
    source_type: "BuyerInvoice"  # Polymorphic stores base class.

class InvoiceLine < ApplicationRecord
  belongs_to :invoicable, polymorphic: true  # E.g. Item
  belongs_to :invoice, polymorphic: true  # BuyerInvoice or SellerInvoice
end

For context, ItemInvoice is a STI subclass of BuyerInvoice. There is a buyer_invoices table. SellerInvoice/seller_invoices are a completely separate table and inheritance chain.

Now, try this:

Item.joins(:item_invoices).to_a

Expected behavior

It works, returning an array of invoices.

Actual behavior

It raises

ActiveRecord::StatementInvalid: PG::UndefinedTable: ERROR: missing FROM-clause entry for table “buyer_invoices”

The reason is that it applies the scope block twice. Both when joining invoice_lines and when joining buyer_invoices.

But the condition in the scope block is not valid when joining invoice_lines (there’s no buyer_invoices table to reference yet) and so it causes this error.

This is the query (I renamed some things to simplify for this example, believe it or not, so I hope I got it right):

SELECT "items".* FROM "items"
INNER JOIN "invoice_lines"
  ON "invoice_lines"."invoicable_type" = 'Item'
  AND "invoice_lines"."invoice_type" = 'BuyerInvoice'
  AND "invoice_lines"."invoicable_id" = "items"."id"
  AND "invoices"."type" = 'ItemInvoice'  -- This shouldn't be here!
INNER JOIN "invoices"
  ON "invoices"."type" = 'ItemInvoice'
  AND "invoices"."id" = "invoice_lines"."invoice_id"

Note that the "invoices"."type" = 'ItemInvoice' condition is included both when joining invoice lines and when joining invoices. In Rails 5.0, it was only included when joining invoices, and then the query worked fine.

some_item.item_invoices works fine. The issue happens when joining (or eager_loading, or includeing if it chooses to use JOINs).

We’ve worked around it by joining an association without the condition, and then adding the condition on top: https://stackoverflow.com/a/63506810/6962 But this makes our code uglier and adds duplication.

System configuration

Rails version: 6.0.3.2

Ruby version: 2.6.6

About this issue

  • Original URL
  • State: open
  • Created 4 years ago
  • Comments: 20 (14 by maintainers)

Most upvoted comments

Hi @joshuay03. Thanks so much for looking into this! I won’t be in a position to look at this for several weeks but I will let you know.

Hey @henrik I came across this issue and thought I’d have a look. I’ve managed to reproduce the scenario in https://github.com/rails/rails/issues/34613 with a failing test, however, having an ambiguous column reference seems to be cause in that case? and can be easily fixed (see https://github.com/rails/rails/pull/48757#discussion_r1266687230).

I might need some help updating the test to reflect your scenario though, which seems a bit different?

@henrik thanks for the reproduction script

Can you provide a reproduction script as well.