Rails 6.1 adds query method associated to check for the association presence


Rails 6.1 adds query method associated similar to ActiveRecord::Relation#missing. missing checks for the orphan objects, while associated checks for the association presence.

Example

Let us consider the following models.

# app/models/manager.rb
class Manager < ApplicationRecord
  has_many :job_listings
end
# app/models/job_listing.rb
class JobListing < ApplicationRecord
  has_many :job_applications
  belongs_to :manager
end
# app/models/job_application.rb
class JobApplication < ApplicationRecord
  belongs_to :job_listing
end

Before Rails 6.1

Now let us try to find all the job listings which have a manager assigned.

[1] pry(main)> JobListing.joins(:manager).where.not(managers: {id: nil})
JobListing Load (0.2ms)  SELECT "job_listings".* FROM "job_listings"
INNER JOIN "managers" ON "managers"."id" = "job_listings"."manager_id"
WHERE "managers"."id" IS NOT NULL LIMIT ?  [["LIMIT", 11]]
=> #<ActiveRecord::Relation [#<Manager id: 3, name: "Jane Doe", created_at: "2020-01-20 14:31:16", updated_at: "2020-01-20 14:31:16">]>

After Rails 6.1

Rails 6.1 adds a query method associated to the ActiveRecord::QueryMethods::WhereChain class.

From the example above, let us find all the job listings which have a manager assigned.

[1] pry(main)> JobListing.where.associated(:manager)
JobListing Load (0.1ms)  SELECT "job_listings".* FROM "job_listings"
INNER JOIN "managers" ON "managers"."id" = "job_listings"."manager_id"
WHERE "managers"."id" IS NOT NULL LIMIT ?  [["LIMIT", 11]]
=> #<ActiveRecord::Relation [#<Manager id: 3, name: "Jane Doe", created_at: "2020-01-20 14:31:16", updated_at: "2020-01-20 14:31:16">]>
[2] pry(main)>

It is just syntactic sugar on top because it also returns a relation with inner join and where clause to check for the presence.

We can also pass multiple relation names to the method.

For example, to find job listings which are having both a manager and a job application:

[1] pry(main)> JobListing.where.associated(:manager, :job_applications)
JobListing Load (0.1ms)  SELECT "job_listings".* FROM "job_listings"
INNER JOIN "managers" ON "managers"."id" = "job_listings"."manager_id"
INNER JOIN "job_applications" ON "job_applications"."job_listing_id" = "job_listings"."id"
WHERE "managers"."id" IS NOT NULL AND "job_applications"."id" IS NOT NULL LIMIT ?  [["LIMIT", 11]]
  => #<ActiveRecord::Relation []>
[2] pry(main)>

In the example above, even though we have a job listing where a manager is assigned, an empty relation is returned because that job listing had no job applications.