I have a pretty simple HABTM set of models
class Tag < ActiveRecord::Base
has_and_belongs_to_many :posts
end
class Post < ActiveRecord::Base
I would prefer to adjust the model and create the classes this way:
class Tag < ActiveRecord::Base
has_many :taggings
has_many :posts, :through => :taggings
end
class Post < ActiveRecord::Base
has_many :taggings
has_many :tags, :through => :taggings
end
class Tagging < ActiveRecord::Base
belongs_to :tag
belongs_to :post
end
Then I would wrap the creation in logic so that Tag models were reused if it existed already. I'd probably even put a unique constraint on the tag name to enforce it. That makes it more efficient to search either way since you can just use the indexes on the join table (to find all posts for a particular tag, and all tags for a particular post).
The only catch is that you can't allow renaming of tags since changing the tag name would affect all uses of that tag. Make the user delete the tag and create a new one instead.
I worked around this by creating a before_save filter that fixes stuff up.
class Post < ActiveRecord::Base
has_and_belongs_to_many :tags
before_save :fix_tags
def tag_list= (tag_list)
self.tags.clear
tag_list.strip.split(' ').each do
self.tags.build(:name => tag)
end
end
def fix_tags
if self.tags.loaded?
new_tags = []
self.tags.each do |tag|
if existing = Tag.find_by_name(tag.name)
new_tags << existing
else
new_tags << tag
end
end
self.tags = new_tags
end
end
end
It could be slightly optimised to work in batches with the tags, also it may need some slightly better transactional support.
This is really old but I thought I'd share my way of doing this.
class Tag < ActiveRecord::Base
has_and_belongs_to_many :posts
end
class Post < ActiveRecord::Base
has_and_belongs_to_many :tags
end
In the code where I need to add tags to a post, I do something like:
new_tag = Tag.find_by(name: 'cool')
post.tag_ids = (post.tag_ids + [new_tag.id]).uniq
This has the effect of automatically adding/removing tags as necessary or doing nothing if that's the case.
In Rails4:
class Post < ActiveRecord::Base
has_and_belongs_to_many :tags, -> { uniq }
(beware, the -> { uniq }
must be directly after the relation name, before other params)
Rails documentation
You should add an index on the tag :name property and then use the find_or_create method in the Tags#create method
docs
Just add a check in your controller before adding the record. If it does, do nothing, if it doesn't, add a new one:
u = current_user
a = @article
if u.articles.exists?(a)
else
u.articles << a
end
More: "4.4.1.14 collection.exists?(...)" http://edgeguides.rubyonrails.org/association_basics.html#scopes-for-has-and-belongs-to-many