Rails / ActiveRecord:通过复合主键急切加载连接表条目

时间:2015-10-28 21:18:57

标签: ruby-on-rails activerecord eager-loading composite-primary-key

我在Rails(4.2.4)(带MySQL)中急切地使用ActiveRecord加载连接表时遇到问题。问题是连接表使用了复合主键,这会导致一些奇怪的问题?急切负荷的行为。这是一个澄清的例子:

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是一个经典的多对多连接表。这里有趣的是,如果course_student_joins表上的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设置,甚至还有一个&#39;解决方案&#39;更改加载以反映它在rails 2.0及更低版本中的执行方式。也使用包含/引用。没有运气,任何/所有帮助表示赞赏。

最终目标是提供一个ActiveRecord课程集合,其中包括热心加入的学生和连接表中的条目,因为我们需要在ActiveModel :: Serializer中的连接表中操作一些值,我们将通过收集。

3 个答案:

答案 0 :(得分:0)

最友好的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

答案 1 :(得分:0)

感觉您不需要连接表的主键。您只需在迁移文件中指定Factories即可。

还要注意更改数据库参数()而不告诉有关这些更改的rails架构,您可以通过干运行来id: false

更新架构

您要寻找的最后一件事是HABTM关系。 has_and_belongs_to_many:http://apidock.com/rails/ActiveRecord/Associations/ClassMethods/has_and_belongs_to_many

您需要的只是:

rake db:migrate

答案 2 :(得分:0)

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