Rails Result with Association, Only Return Pre-Loaded Associations

40 views Asked by At

I have a parent with many children. I preload a sub-set of children with an Active Record query. When I call parent.children I only want the sub-set to be returned rather triggering an N+1 query and returning all of the children.

parents = Parent.includes(:child).where(child: { attr: value })

parents.flat_map do |parent|
  parent.child # causes N+1
end

Context / Use Case

I'm working on a capacity report for a booking system. If there's a pre-calculated Occupancy for a given day I want to show the Occupancy (child) stats, if not I want to show the pre-set for the Resource (parent).

class Resource < ApplicationRecord
  has_many :occupancies
end

class Occupancy < ApplicationRecord
  belongs_to :resource
end

report_start_date = Date.today
report_end_date = report_start_date + 2.weeks
@resources = Resource.includes(:occupancies).where(occupancies: { date: report_start_date...report_end_date }).or(Resource.all).uniq

Within the @resources variable assignment above I want to pre-load occupancies for the reporting period (not outside the period) and I still want Resources that don't have occupancies within the reporting period.

So, although @Schwern's answer makes sense, it doesn't work for this use case - whereby I still need all the Resources.

1

There are 1 answers

0
Schwern On

If you want all children which match a certain criteria, and their parents, in a single query, query the children and preload their parent.

children = Child
  .includes(:parent)
  .where(attr: value)

UPDATE

If you want to only use associations which have already been eager loaded, you can check with loaded?.

@resources = Resource
  .includes(:occupancies)
  .where(occupancies: { date: report_start_date...report_end_date })
  .or(Resource.all)
  .uniq

@resources.find_each do |resource|
  puts resource.occupancies if resource.occupancies.loaded?
end