Rails:将多对多表导出为ex​​cel的更有效方法

时间:2014-03-25 17:43:05

标签: sql ruby-on-rails has-many-through

我有以下型号:

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),但通常的方法并不起作用。

我希望在更少的查询中提取我需要的所有数据。有人有个主意吗?

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 %>
    ...