Rails associations NOT EXISTS. Better way?
There are a number of ways to do NOT EXISTS in rails 5, 6:
- distinct items OUTER JOIN item_owners where item_owners.id is null
- items.id NOT IN (select item_id from item_owners)
- NOT EXISTS (select 1 from item_owners where item_id = items.id)
- where (select COUNT(*) from item_owners where item_id = items.id) = 0
Off my head I can think of 4 approaches, but I seem to remember there being 7. Anyway, this is a tangent but may give you some ideas that work better for your use case.
I found using the NOT IN approach was the easiest for my team to create and maintain. Our goals were to avoid arel, support WHERE clauses in the owner table (e.g.: admin owner), and supporting multiple levels of rails :through.
Items.where.not(id: Items.joins(:owners).select(:id))
.select(:id, :name)
Items.where.not(id: Items.joins(:items_owners).select(:id))
.select(:id, :name)
Items.where.not(id: ItemOwners.select(:item_id))
We use the first, but those examples should be in order from least optimized to best. Also in order from least knowledge of the models to the most.
Items.joins(:organization).includes(:owners).references(:owners).
where('owners.id IS NULL')
And if you want to use includes
for both:
Items.includes(:organization, :owners).references(:organization, :owners).
where('organisations.id IS NOT NULL AND owners.id IS NULL')
And as @Dario Barrionuevo wrote, it should be belongs_to :organisation
in Item.
Using arel_table
in the first example:
Items.joins(:organization).includes(:owners).references(:owners).
where(Owner.arel_table[:id].eq(nil))
In Rails 5 (from comment by @aNoble):
Items.joins(:organization).left_joins(:owners).
where(Owner.arel_table[:id].eq(nil))
But using includes
is still preferable if the relations should be referenced in the code, to avoid extra reads.