ruby-on-railsrubyscopeassociationsscopes

How to select from a model all elements that have no relation to another model


Sorry for the vague title. I have 3 tables: User, Place and PlaceOwner. I want to write a scope in the "PlaceOwner" model to get all the "Places" that don't have an owner.

class User < ApplicationRecord
 has_one :place_owner
end
class PlaceOwner < ApplicationRecord
  belongs_to :user
  belongs_to :place

  #scope :places_without_owner, -> {}
class Place < ApplicationRecord
  has_many :place_owners
end

I tried checking for association in the rails console for each element and it worked. But I don't know how to implement this at scope. I've seen people solve similar problems by writing SQL but I don't have the knowledge to write it that way. I understand that I need to check all IDs from Place to see if they are in the PlaceOwner table or not. But I can't implement it.

For example:

There are 3 records in the "Place" table: House13, House14, House15.

There are 2 records in the "PlaceOwner" table: House13 - User1, House 14 - User2

I want to get House15

I hope I explained clearly what I'm trying to do. Please help or at least tell me where to go. Thanks in advance!


Solution

  • I would use the ActiveRecord::QueryMethods::WhereChain#missing method which was introduced in Ruby on Rails 6.1:

    Place.where.missing(:place_owners)
    

    Quote from the docs:

    missing(*associations)

    Returns a new relation with left outer joins and where clause to identify missing relations.

    For example, posts that are missing a related author:

    Post.where.missing(:author)
    # SELECT "posts".* FROM "posts"
    # LEFT OUTER JOIN "authors" ON "authors"."id" = "posts"."author_id"
    # WHERE "authors"."id" IS NULL