This seems fairly simple but I can't get it to turn up on Google.
If I have:
class City < ActiveRecord::Base
has_many :photos
end
class Photo < ActiveRecord::Base
belongs_to :city
end
I want to find all cities that have no photos. I'd love to be able to call something like...
City.where( photos.empty? )
...but that doesn't exist. So, how do you do this kind of query?
Update: Having now found an answer to the original question, I'm curious, how do you construct the inverse?
IE: if I wanted to create these as scopes:
scope :without_photos, includes(:photos).where( :photos => {:city_id=>nil} )
scope :with_photos, ???
Bah, found it here: https://stackoverflow.com/a/5570221/417872
City.includes(:photos).where(photos: { city_id: nil })
In Rails versions >= 5, to find all cities that have no photos, you can use left_outer_joins
:
City.left_outer_joins(:photos).where(photos: {id: nil})
which will result in SQL like:
SELECT cities.*
FROM cities LEFT OUTER JOIN photos ON photos.city_id = city.id
WHERE photos.id IS NULL
Using includes
:
City.includes(:photos).where(photos: {id: nil})
will have the same result, but will result in much uglier SQL like:
SELECT cities.id AS t0_r0, cities.attr1 AS t0_r1, cities.attr2 AS t0_r2, cities.created_at AS t0_r3, cities.updated_at AS t0_r4, photos.id AS t1_r0, photos.city_id AS t1_r1, photos.attr1 AS t1_r2, photos.attr2 AS t1_r3, photos.created_at AS t1_r4, photos.updated_at AS t1_r5
FROM cities LEFT OUTER JOIN photos ON photos.city_id = cities.id
WHERE photos.id IS NULL
When trying to find records with no matching records from the joined table, you need to use a LEFT OUTER JOIN
scope :with_photos, joins('LEFT OUTER JOIN photos ON cities.id = photos.city_id').group('cities.id').having('count(photos.id) > 0')
scope :without_photos, joins('LEFT OUTER JOIN photos ON cities.id = photos.city_id').group('cities.id').having('count(photos.id) = 0')
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With