3

Please notice, this is not a question about adding a WHERE condition to an association but a rather advanced question about how to alter the JOIN clause when using eager_load.

Lets say I have these models

class Parent
  has_many :children
  have_many :grades, through: :children
end

class Child
  belongs_to :parent
end

class Grade
  belongs_to :child
end

So to eager load the parents I would do:

Parent.eager_load(:grades)

But if I wanted all the parents - but only to eager load the highest scoring like so:

LEFT OUTER JOIN grades ON grades.child_id = children.id AND grades.level = 'A+'

I have tried using includes(:children).joins("LEFT OUTER JOIN grades ON grades.child_id = children.id AND grades.level = 'A+'") but since Rails does not build the association objects it will cause an extra query for each parent.

I have not found any references on using eager_load with a custom SQL string and have dug through the source without getting any wiser.

max
  • 96,212
  • 14
  • 104
  • 165

2 Answers2

5

If I am right, you only want to eager load the subset of the associations. Well, in order to do so, you need to outsmart the rails . You can basically create another association such as "highest_grade" to fetch the grades with level A+ using a where clause inside the ruby lambda. This can be achieved by using the following snippet of code

class Parent
  has_many :children
  has_many :grades, through: :children
  has_many :highest_grade, ->{where(level: 'A+')}, class_name: "Grade", through: :children
end

Now you need to eager load the highest_grade association.

 Parent.eager_load(:highest_grades)

This will essentially load all the parents along with only those grades whose level is A+.

It generates the following query.

 SQL (0.3ms)  SELECT `parents`.`id` AS t0_r0, `parents`.`created_at` AS    t0_r1, `parents`.`updated_at` AS t0_r2, `grades`.`id` AS t1_r0,    `grades`.`child_id` AS t1_r1, `grades`.`created_at` AS t1_r2, `grades`.`updated_at` AS t1_r3, `grades`.`level` AS t1_r4 FROM `parents` LEFT OUTER JOIN `children` ON `children`.`parent_id` = `parents`.`id` LEFT OUTER JOIN `grades` ON `grades`.`child_id` = `children`.`id` AND `grades`.`level` = '

If you want to understand how this works. Please use this link as a reference

Shark Lasers
  • 441
  • 6
  • 15
Ankush
  • 146
  • 8
1

EDIT

This does not solve the problem stated.


You cannot alter the JOIN generated by calling eager_load like you want. The WHERE clause is your only option, and it has the exact same effect as the condition on your JOIN. Here it is, even if you didn't want it:

Parent.eager_load(:grades).where("grades.level = 'A+'")
Community
  • 1
  • 1
Wizard of Ogz
  • 12,543
  • 2
  • 41
  • 43