Reputation: 2775
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
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
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