Dave
Dave

Reputation: 2775

Rails - Eager Load Association on an Association

EDIT - Using 'includes' generates a SQL 'IN' clause. When using Oracle this has a 1,000 item limit. It will not work for my company. Are there any other solutions out there?

Is it possible to eager load an association on an association?

For example, let's say I have an Academy class, and an academy has many students. Each student belongs_to student_level

class Academy < ActiveRecord::Base
  has_many :students
end

class Student < ActiveRecord::Base
  belongs_to :academy
  belongs_to :student_level
end

class StudentLevel < ActiveRecord::Base
  has_many :students
end

Is it possible to tailor the association in Academy so that when I load the students, I ALWAYS load the student_level with the student?

In other words, I would like the following section of code to produce one or two queries total, not one query for every student:

@academy.students.each do |student|
  puts "#{student.name} - #{student.student_level.level_name}"
end

I know I can do this if I change students from an association to a method, but I don't want to do that as I won't be able to reference students as an association in my other queries. I also know that I can do this in SQL in the following manner, but I want to know if there's a way to do this without finder_sql on my association, because now I need to update my finder_sql anytime my default scope changes, and this won't preload the association:

SELECT students.*, student_levels.* FROM students
LEFT JOIN student_levels ON students.student_level_id = student_levels.id
WHERE students.academy_id = ACADEMY_ID_HERE

Upvotes: 2

Views: 3654

Answers (1)

house9
house9

Reputation: 20624

Have you tried using includes to eager load the data?

class Academy < ActiveRecord::Base
  has_many :students

  # you can probably come up with better method name
  def students_with_levels
    # not sure if includes works off associations, see alternate below if it does not
    self.students.includes(:student_level)
  end

  def alternate
    Student.where("academy_id = ?", self.id).includes(:student_level)
  end
end

see also: http://guides.rubyonrails.org/active_record_querying.html#eager-loading-associations

should result in 3 queries

  • the initial find on Academy
  • the query for a collection of Student objects
  • the query for all of those Students StudentLevel objects

Additions:

# the same can be done outside of methods
@academy.students.includes(:student_level).each do |student|
  puts "#{student.name} - #{student.student_level.level_name}"
end

Student.where("academy_id = ?", @academy.id).includes(:student_level).each do |student|
  puts "#{student.name} - #{student.student_level.level_name}"
end

ActiveRelation queries are also chainable

@academy.students_with_levels.where("name ILIKE ?", "Smi%").each do # ...

Sort of related a nice article on encapsulation of ActiveRecord queries (methods) - http://ablogaboutcode.com/2012/03/22/respect-the-active-record/

Upvotes: 5

Related Questions