0

I'm trying to do something fairly simple. I have two models, User and Group. For simplicity's sake, let's say they look like this:

class User < ActiveRecord::Base
  has_and_belongs_to_many :groups
end

and

class Group < ActiveRecord::Base
  has_and_belongs_to_many :users
end

Now, for some reason, I have a user that has the same group twice. In the Rails Console:

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

And there is some SQL output that I've silenced. I would think that everything should be all set, but it's not. The groups aren't updated, and that user still has both. I could go into the join table and manually remove the duplicates, but that seems cludgy and gross and unnecessary. What am I doing wrong here?

I'm running Rails 3.2.11 and Ruby 1.9.3p392

Additional note: I've tried this many different ways, including using user.update_attributes, and using group_ids instead of the groups themselves, to no avail.

BSprague
  • 5
  • 4
  • That was a typo in posting this, in the console the variable name stayed consistent. – BSprague Jun 18 '13 at 16:31
  • Thought I'd check the easy stuff first. ;-) – pjmorse Jun 18 '13 at 16:32
  • adding 'uniq: true' to the relation ships will help u to 'get' the uniq records but it wont stop form creating duplicates. see http://stackoverflow.com/questions/1129781/has-and-belongs-to-many-avoiding-dupes-in-the-join-table – Prasad Surase Jun 18 '13 at 17:38
  • That's good to know, thank you. But is there any explanation as to why this doesn't work, and how I would make it work? – BSprague Jun 18 '13 at 18:25

2 Answers2

1

The reason this doesn't work is because ActiveRecord isn't handling the invalid state of duplicates in the habtm association (or any CollectionAssociation for that matter). Any ids not included in the newly assigned array are deleted - but there aren't any in this case. The relevant code:

# 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

The 'targets' being passed around are Arrays of assigned records. Note the call to delete(target - new_target) is equivalent in your case to delete(user.groups - user.groups.uniq) which results in an empty Array passed (since comparison is based on the id attribute of each record).

Instead, you'll need to clear out the association and then reassign the single group again:

group = user.groups.first
user.groups.clear
user.groups << group
PinnyM
  • 35,165
  • 3
  • 73
  • 81
0

This might be a way to cleanup those duplicates (it handles any number of groups of duplicate associations):

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)}
trushkevich
  • 2,657
  • 1
  • 28
  • 37