Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

has_and_belongs_to_many, avoiding dupes in the join table

I have a pretty simple HABTM set of models

class Tag < ActiveRecord::Base     has_and_belongs_to_many :posts end   class Post < ActiveRecord::Base     has_and_belongs_to_many :tags     def tags= (tag_list)        self.tags.clear        tag_list.strip.split(' ').each do          self.tags.build(:name => tag)        end    end  end  

Now it all works alright except that I get a ton of duplicates in the Tags table.

What do I need to do to avoid duplicates (bases on name) in the tags table?

like image 504
Sam Saffron Avatar asked Jul 15 '09 06:07

Sam Saffron


1 Answers

Prevent duplicates in the view only (Lazy solution)

The following does not prevent writing duplicate relationships to the database, it only ensures find methods ignore duplicates.

In Rails 5:

has_and_belongs_to_many :tags, -> { distinct } 

Note: Relation#uniq was depreciated in Rails 5 (commit)

In Rails 4

has_and_belongs_to_many :tags, -> { uniq } 

Prevent duplicate data from being saved (best solution)

Option 1: Prevent duplicates from the controller:

post.tags << tag unless post.tags.include?(tag) 

However, multiple users could attempt post.tags.include?(tag) at the same time, thus this is subject to race conditions. This is discussed here.

For robustness you can also add this to the Post model (post.rb)

def tag=(tag)   tags << tag unless tags.include?(tag) end 

Option 2: Create a unique index

The most foolproof way of preventing duplicates is to have duplicate constraints at the database layer. This can be achieved by adding a unique index on the table itself.

rails g migration add_index_to_posts # migration file add_index :posts_tags, [:post_id, :tag_id], :unique => true add_index :posts_tags, :tag_id 

Once you have the unique index, attempting to add a duplicate record will raise an ActiveRecord::RecordNotUnique error. Handling this is out of the scope of this question. View this SO question.

rescue_from ActiveRecord::RecordNotUnique, :with => :some_method 
like image 117
Jeremy Lynch Avatar answered Sep 24 '22 13:09

Jeremy Lynch