Rails/ActiveRecord:通过复合主键预先加载连接 table 条目

Rails/ActiveRecord: Eager loading join table entries via a composite primary key

我在 Rails (4.2.4) (w/MySQL) 中使用 ActiveRecord 急切加载连接 table 时遇到问题。问题是 join table 使用了复合主键,这导致了一些奇怪的事情?急切加载的行为。这里有一个例子来说明:

class Student < ActiveRecord::Base
  has_many :course_student_joins
  has_many :courses, through: :course_student_joins
end

class Course < ActiveRecord::Base
  has_many :course_student_joins
  has_many :students, through: :course_student_joins
end

class CourseStudentJoin < ActiveRecord::Base
  belongs_to :course
  belongs_to :student
end

Table: courses

id  name
1   Course 1
2   Course 2

Table: students

id  name
1   Joey
2   Johnny
3   Dee Dee

Table: course_student_joins

course_id   student_id  extra_id
    1           1           5
    2           2           6

所以course_student_joins是经典的多对多连接table。这是有趣的一点,如果 course_student_joins table 上的 PRIMARY KEY 仅由 course_id(单个字段)组成,这有效:

> Course.eager_load(:course_student_joins).first.course_student_joins

Result:

  SQL (0.2ms)  SELECT `courses`.`id` AS t0_r0, `courses`.`name` AS t0_r1, `course_student_joins`.`course_id` AS t1_r0, `course_student_joins`.`student_id` AS t1_r1, `course_student_joins`.`extra_id` AS t1_r2 FROM `courses` LEFT OUTER JOIN `course_student_joins` ON `course_student_joins`.`course_id` = `courses`.`id` WHERE `courses`.`id` = 1 AND `courses`.`id` IN (1)
 => #<ActiveRecord::Associations::CollectionProxy [#<CourseStudentJoin course_id: 1, student_id: 1, extra_id: 5>]> 

如果PRIMARY KEY被(数据库端)更改为(course_id,student_id)的复合键,这就是我们想要的(因为一个课程不能有相同的学生两次并且主键需要是唯一的),这就是我们得到的:

> Course.eager_load(:course_student_joins).find_by(id:1).course_student_joins

Result:

  SQL (0.2ms)  SELECT `courses`.`id` AS t0_r0, `courses`.`name` AS t0_r1, `course_student_joins`.`course_id` AS t1_r0, `course_student_joins`.`student_id` AS t1_r1, `course_student_joins`.`extra_id` AS t1_r2 FROM `courses` LEFT OUTER JOIN `course_student_joins` ON `course_student_joins`.`course_id` = `courses`.`id` WHERE `courses`.`id` = 1 AND `courses`.`id` IN (1)
 => #<ActiveRecord::Associations::CollectionProxy []> 

注意第二个结果中的空集合,SQL 在两个结果中保持完全相同(预期)。

我们在 has_many 声明中使用了 foreign_key、primary_key 设置,甚至 'solution' 改变了加载以反映它是如何执行的rails 2.0 及以下版本。也玩过 includes/references 。运气不好,any/all 帮助表示感谢。

最终目标是提出一个 ActiveRecord 课程集合,其中包含急切加入的学生和连接中的条目 table,因为我们需要对连接中的某些值进行操作 table 在一个 ActiveModel::Serializer 中,我们将通过它传递集合。

最 Rails 友好的方法是自动递增 id 整数主键 + (course_id, student_id) 上的唯一索引。

您是否有任何理由想要加载和 CourseStudentJoin 记录?无论如何,您似乎不想直接操作它们,最好还是做

这样的事情
Course.includes(:students).where(active: true, category: "Math").foo.bar
Student.includes(:courses).where(state: "CA").foo.bar

感觉您不需要主键来进行联接 table。您可以只在迁移文件上指定 id: false

还要注意更改数据库参数 () 而不告诉 rails schema 这些更改,您可以通过 dry 运行 rake db:migrate

更新 schema

你最后要找的是 HABTM 关系。 has_and_belongs_to_many: http://apidock.com/rails/ActiveRecord/Associations/ClassMethods/has_and_belongs_to_many

您只需要:

student = Student.find(1)
student.courses

跟进。将一个单一的、自动递增的列作为永远不会被使用的主键,不太合适。 composite_primary_keys gem 确实影响了 ActiveRecord 中的 eager_load 机制并解决了这个问题。我们没有考虑它,因为我们不需要 gem 的主要功能,但它确实以最可接受的方式解决了这个问题。