Reputation: 1793
Suppose I have a Parent
model that has many Child
, and that Child
also belongs to OtherParent
.
How can i find all Parent
where ALL of its Child
belongs to any OtherParent
?
In pure SQL I could do
Parent.find_by_sql(<<SQL)
SELECT *
FROM parents p
WHERE NOT EXISTS (
SELECT *
FROM children
WHERE parent_id = p.id
AND other_parent_id IS NULL
)
SQL
(from here), but I'd prefer to do it by taking advantage of ActiveRecord if possible.
Thanks!
I'm using Rails 4.2.1 and PostgreSQL 9.3
Upvotes: 6
Views: 2786
Reputation: 9485
Using arel
can get you pretty far. The tricky part is how do you not write your entire query using arel
's own query syntax?
Here's a trick: when building your query using where
, if you use arel
conditions, you get some extra methods for free. For instance, you can tail the subquery you have there with .exists.not
, which will get you a (NOT ( EXISTS (subquery)))
Toss that into parent's where
-clause and you're set.
The question is, how do you reference the tables involved? You need Arel for that. You could use Arel's where
with its ugly conditions like a.eq b
. But why? Since it's an equality condition, you can use Rails' conditions instead! You can reference the table you're quering with a hash key, but for the other table (in the outer query) you can use its arel_table
. Watch this:
parents = Parent.arel_table
Parent.where(
Child.where(other_parent_id: nil, parent_id: parents[:id]).exists.not
)
You can even reduce Arel usage by resorting to strings a little and relying on the fact that you can feed in subqueries as parameters to Rails' where
. There is not much use to it, but it doesn't force you to dig into Arel's methods too much, so you can use that trick or other SQL operators that take a subquery (are there even any others?):
parents = Parent.arel_table
Parent.where('NOT EXISTS (?)',
Child.where(parent_id: parents[:id], other_parent_id: nil)
)
The two main points here are:
where
method just fine.Upvotes: 3
Reputation: 5595
Using the exceptional scuttle, you can translate arbitrary SQL into ruby (ActiveRecord and ARel queries)
From that tool, your query converts to
Parent.select(Arel.star).where(
Child.select(Arel.star).where(
Child.arel_table[:parent_id].eq(Parent.arel_table[:id]).and(Child.arel_table[:other_parent_id].eq(nil))
).ast
)
Splitting up the query-
Parent.select(Arel.star)
will query for all columns in the Parent table.Child.arel_table
brings you into Arel-world, allowing you a little bit more power in generating your query from ruby. Specifically, Child.arel_table[:parent_id]
gives you a handle onto an Arel::Attributes::Attribute that you can continue to use while building a query..eq
and .and
methods do exactly what you would expect, letting you build a query of arbitrary depth and complexity.Not necessarily "cleaner", but entirely within ruby, which is nice.
Upvotes: 2
Reputation: 1921
Given Parent
and Child
, and child is in a belongs_to
relationship with OtherParent
(Rails defaults assumed):
Parent.joins(:childs).where('other_parent_id = ?', other_parent_id)
Upvotes: -1