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?
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 }
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With