An association defined with has_many :through
will return the same record multiple times if multiple join models for the same record exist (a n:m relation). To prevent this, you need to add ->{ uniq }
as second argument to has_many
(below Rails 4 it is a simple option: has_many :xyz, :uniq => true
).
Example
Say you have an Invoice
with multiple Items
. Each Item
has a Product
:
class Invoice < ActiveRecord::Base
has_many :items
has_many :products, :through => :items
end
class Item < ActiveRecord::Base
belongs_to :invoice
belongs_to :product
end
class Product < ActiveRecord::Base
has_many :items
end
Further say you have multiple items for the same product and invoice in the items
table:
id | invoice_id | product_id |
---|---|---|
11 | 1001 | 55 |
12 | 1001 | 55 |
Now #products
will return the product with ID #55 two times for this invoice:
invoice = Invoice.find(1001)
invoice.products # => [#<Product id: 55>, #<Product id: 55>]
You probably wanted Invoice#products
to mean "all products involved with the invoice", so you need to use the :uniq
option of has_many
:
class Invoice < ActiveRecord::Base
has_many :items
has_many :products, :through => :items, :uniq => true
end
Now the product is only returned once:
invoice = Invoice.find(1001)
invoice.products # => [#<Product id: 55>]
How the :uniq option works
In the example above it changes the MySQL query for Invoice#products
to something like
SELECT DISTINCT * FROM products ...
Issues with PostgreSQL
In Postgres, SELECT DISTINCT * FROM products ...
will fail when any column of products
is of type json
:
ActiveRecord::StatementInvalid Exception: PG::UndefinedFunction: ERROR: could not identify an equality operator for type json