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 join
ing (or eager_load
ing, or include
ing 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)
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.