【问题标题】:ActiveRecord has_many :through duplicating counter caches on mass assignmentActiveRecord has_many:通过在批量分配上复制计数器缓存
【发布时间】:2012-03-17 13:51:23
【问题描述】:

似乎 ActiveRecord 的 counter_cache 功能可以导致计数器缓存增加两次。我看到这种行为的场景是当我有两个模型通过连接模型彼此具有has_many :through 关系时(即:Teacher 有许多StudentClassroom)。当使用has_many :through 生成的方法直接关联教师和学生(无需手动创建连接记录)时,计数增加了 2 倍。示例:teacher.students << Student.create(name: "Bobby Joe") 导致 teacher.students_count 增加 2。

请帮助我找到一个解决方案来缓解或消除此问题,同时允许我继续使用内置的计数器缓存和通过has_many :through 关系进行批量分配。

我花了很多时间寻找解决方案,并将问题提取到一个小型测试应用程序中,这是我可以创建的最简单的失败示例。希望帮助我解决此问题所需的任何其他详细信息都应该在下面。

示例架构和模型:

create_table :teachers do |t|
  t.string  :name
  t.integer :students_count, default: 0
  t.timestamps
end

class Teacher < ActiveRecord::Base
  has_many :classrooms
  has_many :students, :through => :classrooms
end

create_table :students do |t|
  t.string  :name
  t.integer :teachers_count, default: 0
  t.timestamps
end

class Student < ActiveRecord::Base
  has_many :classrooms
  has_many :teachers, :through => :classrooms
end

create_table :classrooms do |t|
  t.references :teacher
  t.references :student
  t.timestamps
end

class Classroom < ActiveRecord::Base
  belongs_to :student, :counter_cache => :teachers_count
  belongs_to :teacher, :counter_cache => :students_count
end

这是一个简短的 Rails 控制台会话,显示了所采取的步骤以及 Rails 正在执行 两个 更新 teachers 以增加 students_count 的事实:

1.9.2-p290 :001 > t = Teacher.create(name: "Miss Nice")
  SQL (9.7ms)  INSERT INTO "teachers" ("created_at", "name", "students_count", "updated_at") VALUES (?, ?, ?, ?)  [["created_at", Tue, 28 Feb 2012 03:31:53 UTC +00:00], ["name", "Miss Nice"], ["students_count", 0], ["updated_at", Tue, 28 Feb 2012 03:31:53 UTC +00:00]]
 => #<Teacher id: 1, name: "Miss Nice", students_count: 0, created_at: "2012-02-28 03:31:53", updated_at: "2012-02-28 03:31:53"> 
1.9.2-p290 :002 > t.students << Student.new(name: "Mary Ann")
  SQL (0.3ms)  INSERT INTO "students" ("created_at", "name", "teachers_count", "updated_at") VALUES (?, ?, ?, ?)  [["created_at", Tue, 28 Feb 2012 03:32:12 UTC +00:00], ["name", "Mary Ann"], ["teachers_count", 0], ["updated_at", Tue, 28 Feb 2012 03:32:12 UTC +00:00]]
  SQL (0.3ms)  INSERT INTO "classrooms" ("created_at", "student_id", "teacher_id", "updated_at") VALUES (?, ?, ?, ?)  [["created_at", Tue, 28 Feb 2012 03:32:12 UTC +00:00], ["student_id", 1], ["teacher_id", 1], ["updated_at", Tue, 28 Feb 2012 03:32:12 UTC +00:00]]
  SQL (0.2ms)  UPDATE "students" SET "teachers_count" = COALESCE("teachers_count", 0) + 1 WHERE "students"."id" = 1
  Teacher Load (0.1ms)  SELECT "teachers".* FROM "teachers" WHERE "teachers"."id" = 1 LIMIT 1
  SQL (0.1ms)  UPDATE "teachers" SET "students_count" = COALESCE("students_count", 0) + 1 WHERE "teachers"."id" = 1
  SQL (0.0ms)  UPDATE "teachers" SET "students_count" = COALESCE("students_count", 0) + 1 WHERE "teachers"."id" = 1
  Student Load (0.2ms)  SELECT "students".* FROM "students" INNER JOIN "classrooms" ON "students"."id" = "classrooms"."student_id" WHERE "classrooms"."teacher_id" = 1
 => [#<Student id: 1, name: "Mary Ann", teachers_count: 1, created_at: "2012-02-28 03:32:12", updated_at: "2012-02-28 03:32:12">] 

如果有人想近距离观察,我已将整个测试应用程序放在 github 上 (https://github.com/carlzulauf/test_app)。我还创建了一个单元测试来演示该问题并且未能通过 (https://github.com/carlzulauf/test_app/blob/master/test/unit/classroom_test.rb)

【问题讨论】:

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


【解决方案1】:

到目前为止,我的研究告诉我这可能是一个错误。以下是已针对此问题提交的一些 github 问题:

https://github.com/rails/rails/issues/3903

https://github.com/rails/rails/issues/3085

显然有一个由 has_many :through 关系引起的未记录的自动计数器缓存。因此,如果 Teacher.has_many :students, :through =&gt; :classroomsteacher.students &lt;&lt; student 集合分配已经查找并增加 teacher.students_count(如果该列存在)。

如果您添加 Classroom.belongs_to :teacher, :counter_cache =&gt; :students_count,则在创建 Classroom 模型时会触发额外的回调,并且该列会增加 两次

有效的解决方法: 将计数器缓存列重命名为其他名称。 Student#teacherz_countTeacher#studentz_count 有效地让我的 test case 通过。

https://github.com/carlzulauf/test_app/commit/707a33f948d5d55a8aa942e825841fdd8a7e7705

我还没有找到 ActiveRecord 代码库中的问题所在,所以我暂时不会接受我自己的答案,以防有人知道为什么 has_many :through 会这样工作以及有问题的代码在哪里生活。

更新

我相信我发现了有问题的代码行。注释掉这一行可以解决问题:

https://github.com/rails/rails/blob/889e8bee82ea4f75adb6de5badad512d2c615b7f/activerecord/lib/active_record/associations/has_many_through_association.rb#L53

我似乎无法启动并运行边缘导轨,所以我还不能为这个错误提交拉动。如果其他人有能力,请做。

找到有问题的行让我可以在我的测试应用程序中制作更有效的猴子补丁,无需重命名任何列即可解决问题。

https://github.com/carlzulauf/test_app/commit/3c421b035bd032b91ff60e3d74b957651c37c7fa

【讨论】:

  • 太棒了,谢谢!奇怪的行为,如果我需要直接创建带有 floorroom_number 等附加字段的 Classroom 模型怎么办。计数器缓存不会被计算,我必须手动更新。
猜你喜欢
  • 2013-03-21
  • 1970-01-01
  • 2020-10-19
  • 2012-06-23
  • 2014-05-07
  • 2018-03-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多