Home > Software engineering >  Rails Query Joins and Where Not
Rails Query Joins and Where Not

Time:08-09

I have two models, Customer and Driver. There is a join table (as a full model) which is used to blacklist drivers and customers from each other.

class Driver
  has_many :customer_blacklists
  has_many :blacklisted_customers, through: :customer_blacklists, source: :customer
end


class Customer
  has_many :driver_blacklists, class_name: 'CustomerBlacklist', foreign_key: 'customer_id'
  has_many :blacklisted_drivers, through: :driver_blacklists, source: :driver
end

class CustomerBlacklist
  belongs_to :customer
  belongs_to :driver
end

I want to create an efficient query to find all drivers who are not blacklisted by the current customer.

Currently I have either:

Driver.where.not(id: CustomerBlacklist.where(customer_id: customer_id).pluck(:driver_id))

or

Driver.where.not(id: [customer.blacklisted_drivers.pluck(:id)] ) }

Both of these work fine (they're basically the same thing, the second is more readable. But there are two seperate queries happening here.

I've tried without success to make this a single query, my last try looked like:

Driver.left_joins(:customer_blacklists).where.not(customer_blacklists: {driver_id: driver_id, customer_id: customer_id} )

But this returns only Drivers who do have an entry in the customers_blacklists table, but not for the specified customer. It does not include any drivers who have never been blacklisted at all.

The query should return

  • All Drivers who are not blacklisted by anyone
  • All Drivers who are not blacklisted by the current customer

CodePudding user response:

All you actually need is:

Driver.where.not(id: customer.blacklisted_drivers)

ActiveRecord is smart enough to build a subquery when you pass an assocation to .where. .pluck(:id) is largely an anti-pattern as you're preventing the ORM from actually making a better query.

Another alternative to a subquery that can be more performant is a lateral join - however there is no polyglot solution for that so it would be database specific. I would revisit this later if the performance actually becomes a problem.

  • Related