无法删除has_many通过

时间:2014-02-24 19:43:48

标签: ruby-on-rails ruby ruby-on-rails-3

我有三个模特

主题

class Subject < ActiveRecord::Base
  has_many :enrollments, dependent: :destroy
  has_many :students, through: :enrollments, source: :students

用户

class User < ActiveRecord::Base
  has_many :enrollments, foreign_key: 'student_id', dependent: :destroy
  has_many :subjects, through: :enrollments

注册

class Enrollment < ActiveRecord::Base
  belongs_to :subject
  belongs_to :student, class_name 'User'

让我说,首先我做

User.count # return 23

它返回23,这很好。但是如果我做的话

c = Subject.first    # Any subject
s = c.students
s.size          # It returns 1, so it does have AR, and I can see the users.
s.class         # For some reason it is an Array, not an AR, but rails is probably lying
s.destroy_all   # Shows it destroys it successfully

s               # Returns []
c.students      # Returns []

但问题是我打电话

User.count   # It still return 23

计数错了。它应该是22.我仍然可以使用User.find(delete_student_id) 并仍然看到记录。当我使用Use.count时,它不是缓存问题,因为我使用User.all.size,它给出了23。

我希望学生实际上是从数据库中删除的,使用subject.students.destroy_all

现在我正在使用

User.where(id: s.pluck('users.id')).destroy_all

s.each do |student|
  student.destroy
end

这些会起作用,但对我来说是一个很大的代码味道。

2 个答案:

答案 0 :(得分:0)

您需要将Enrollment类更新为依赖销毁用户:

class Enrollment < ActiveRecord::Base
  belongs_to :subject
  belongs_to :student, class_name 'User', dependent: :destroy
end

但是,我不建议这样做,因为只要User注册多个Subjects,它就会抛出错误。

如果您要删除任何未注册的User,我会在after_destroy上使用Enrollment回调。

答案 1 :(得分:0)

destroy_all确实仅在mikerz建议时删除了注册。我重新创建了你的设置并运行了rails console:

>> Subject.first.students.destroy_all
Subject Load (1.0ms)  SELECT `subjects`.* FROM `subjects` LIMIT 1
User Load (0.0ms)  SELECT `users`.* FROM `users` INNER JOIN `enrollments` ON `users`.`id` = `enrollments`.`student_id` WHERE `enrollments`.`subject_id` = 1
   (0.0ms)  BEGIN
  Enrollment Load (0.0ms)  SELECT `enrollments`.* FROM `enrollments` WHERE `enrollments`.`subject_id` = 1 AND `enrollments`.`student_id` = 1
  SQL (22.0ms)  DELETE FROM `enrollments` WHERE `enrollments`.`id` = 2
   (91.0ms)  COMMIT
[#<User id: 1, name: "a user", created_at: "2014-02-24 21:49:05", updated_at: "2014-02-24 21:49:05">]

因此,通过合同,返回已删除的对象,:through关联似乎有点过时了。离开你

Subject.first.students.select.destroy_all

(其中select是切换到删除用户的魔力),

Subject.first.students.each { |student| student.destroy }

User.destroy_all(id: Subject.first.students.collect { |student| student.id })

由于有depent: :destroy,这三个中的前两个似乎运行相同数量的SQL,而第三个将加载每个User两次。
每个变体一次销毁一个注册和一个用户,而不是所有注册(甚至不是单个用户的注册)或一个语句中的所有用户。 destroy_all - API doc:

中描述了此行为
  

注意:当您一次删除多条记录时,实例化,回调执行和删除每条记录都会非常耗时。它为每条记录生成至少一个SQL DELETE查询(或者更多,以强制执行您的回调)。如果要快速删除多行,而不关心它们的关联或回调,请改用delete_all。