从HABTM关联中删除记录

时间:2013-06-18 16:26:33

标签: ruby-on-rails ruby

我正在尝试做一些相当简单的事情。我有两个模型,用户和组。为简单起见,让我们说它们看起来像这样:

class User < ActiveRecord::Base
  has_and_belongs_to_many :groups
end

class Group < ActiveRecord::Base
  has_and_belongs_to_many :users
end

现在,由于某种原因,我有一个用户具有相同的组两次。在Rails控制台中:

user = User.find(1000)

=> #<User id: 1000, first_name: "John", last_name: "Doe", active: true, created_at:
"2013-01-02 16:52:36", updated_at: "2013-06-17 16:21:09">

groups = user.groups

=> [#<Group id: 1, name: "student", is_active: true, created_at: "2012-12-24 15:08:59",
updated_at: "2012-12-24 15:08:59">, #<Group id: 1, name: "student", is_active: true,
created_at: "2012-12-24 15:08:59", updated_at: "2012-12-24 15:08:59">]

user.groups = groups.uniq

=> [#<Group id: 1, name: "student", is_active: true, created_at: "2012-12-24 15:08:59",
updated_at: "2012-12-24 15:08:59">]

user.save

=> true

我已经沉默了一些SQL输出。我认为一切都应该全部设定,但事实并非如此。这些组未更新,并且该用户仍然具有这两者。我可以进入连接表并手动删除重复项,但这似乎很笨重,粗暴和不必要。我在这里做错了什么?

我正在运行Rails 3.2.11和Ruby 1.9.3p392

补充说明:我尝试了很多不同的方法,包括使用user.update_attributes,并使用group_ids而不是组本身,但无济于事。

2 个答案:

答案 0 :(得分:1)

这不起作用的原因是因为ActiveRecord没有处理habtm关联(或任何CollectionAssociation的重复项的无效状态)。新分配的数组中未包含的任何id都将被删除 - 但在这种情况下不存在任何# From lib/active_record/associations/collection_association.rb def replace_records(new_target, original_target) delete(target - new_target) unless concat(new_target - target) @target = original_target raise RecordNotSaved, "Failed to replace #{reflection.name} because one or more of the " \ "new records could not be saved." end target end 。相关代码:

delete(target - new_target)

传递的'目标'是指定记录的数组。请注意,对delete(user.groups - user.groups.uniq)的调用在您的情况下与id相同,这会导致传递空数组(因为比较基于每条记录的group = user.groups.first user.groups.clear user.groups << group 属性)。

相反,您需要清除关联,然后再次重新分配单个组:

{{1}}

答案 1 :(得分:0)

这可能是一种清理这些重复项的方法(它处理任意数量的重复关联组):

user = User.find(1000)

user.groups << user.groups.group_by(&:id).values.find_all {|v| v.size > 1}.each {|duplicates| duplicates.uniq_by! {|obj| obj.id}}.flatten.each {|duplicate| user.groups.delete(duplicate)}