问题
I have a model Indent. On which I am using STI. An indent can be of two types Sale and Purchase. In purchase class I am using an optional has_one association.
class Purchase < Indent
has_one :sale , :class_name => 'Sale', :foreign_key => 'linked_indent_id'
# Make it work.
scope :unsold, lambda {includes(:sale).where('id not in (select distinct linked_indent_id from indents)')}
end
class Sale < Indent
belongs_to :purchase , :class_name => 'Purchase', :foreign_key => 'linked_indent_id'
end
I just need a scope on Purchase class using which i can find all the purchases which are not having a Sale associated to them.
I am using Rails 3.2 and Postgres as database.
Update:
The query which is getting generated is as follows.
SELECT "indents".* FROM "indents" WHERE "indents"."type" IN ('Purchase') AND
(id not in (select distinct linked_indent_id from indents)) ORDER BY indent_date DESC
Following part of the query is working fine.
=# select distinct linked_indent_id from indents;
linked_indent_id
------------------
15013
15019
(3 rows)
And this is also working fine.
SELECT "indents".* FROM "indents" WHERE "indents"."type" IN ('Purchase') AND
(id not in (15013, 15019)) ORDER BY indent_date DESC
What am i missing in coupling these two parts of query?
回答1:
I first got confused by the terms purchase
and sale
. But your update I believe helped me understand the problem more.
So what I understood is anything unsold is purchases minus sales. Following should give you that list:
scope :unsold, lambda {includes(:sale).select { |p| !p.sale.present? } }
Update:
A brief explanation of what's happening here:
The scope does not really do all the work in the database. It does a SQL select of all the purchases including the joined sale first. This gives you all the records in your purchases
table. Then this scope falls back to Ruby Array
on the select
method. The method returns all the purchases p
without a sale
which is done by negating purchases with sale.
Hope this clears up on what the scope is doing a bit.
Update 2:
A scope that's chainable!
scope :unsold, lambda { where('id not in (?)', Sale.pluck(:linked_indent_id)) }
In this scope the id
s of Purchases that are not in Sale
's linked_indent_id
are selected.
回答2:
Rails-ey database-centric way to do this:
scope :sold, -> { joins(:sale) }
scope :unsold, -> { includes(:sale).where(sales: {linked_indent_id: nil}) }
(Note that you have to use the table name in the where
clause, not the relation name. That is 'sales', not 'sale'.)
来源:https://stackoverflow.com/questions/20717000/scope-for-an-optional-has-one-association-with-itself