我有以下型号:
class Student
attr_accessible :name
has_many :courses, through: :course_students
has_many :course_students
end
class Course
attr_accessible :name
has_many :students, through: :course_students
has_many :course_students
end
class CourseStudent
attr_accessible :grade
belongs_to :course
belongs_to :student
end
现在,我正在尝试生成一个excel表,其中学生为行,课程为列,交叉点将显示该课程中学生的成绩。
到目前为止,我正在处理一小组学生,因此以下算法没问题:
<table>
...
<tbody>
<% @students..includes(:courses)each do |student| %>
<tr>
...
<% student.courses.includes(:course_students).each do |course| %>
<td><%= course.course_students.find_by_student_id(student.id).try(:grade) || '-' %></td>
<% end %>
</tr>
<% end %>
</tbody>
</table>
如您所见,我在加载数据时一直试图包含课程和course_students,但我仍然收到大量疑问。我知道这是一个经典的N + 1查询问题(更像是NxM + N + M + 1),但通常的方法并不起作用。
我希望在更少的查询中提取我需要的所有数据。有人有个主意吗?
答案 0 :(得分:1)
首先,您应该将所有课程收集到一些标准化的排序顺序(我正在使用name
任意,但它可以是任何东西):
<% @courses = Course.order(:name) %>
您还应生成标题行以显示课程列:
<tr>
...
<% @courses.each do |course| %>
<td><%= course.name %></td>
<% end %>
</tr>
现在,您需要遍历每个学生(急切加载:course_student加入模型以避免N + 1)
<% @students.includes(:course_students).each do |student| %>
<tr>
...
<% @courses.each do |course| %>
<td><%= student.course_students.detect do |cs|
cs.course_id == course.id
end.try(:grade) || '-' %></td>
<% end %>
</tr>
<% end %>
您可以通过避免调用detect
并为每个学生构建成绩哈希来优化这一点:
...
<% grades = student.course_students.each_with_object(Hash.new('-')) do |cs, hash|
hash[cs.course_id] = cs.grade
end %>
<% @courses.each do |course| %>
<td><%= grades[course.id] %></td>
<% end %>
...