Implementing tags in Rails: How to reference multiple items with one tag?
Asked Answered
B

1

5

I'm writing a blog engine in rails, and have set up a tag model and a post model which have a have_and_belongs_to_many relationship. Tag addition works fine, the problem comes when looking for all posts with a specific tag:

If I add tag "test" to Post A, then add tag "test" to post B, there are two tag objects, both with name "test" but with different IDs, both referencing different posts. Now if I have a controller action indexTagPosts which takes the parameter "tag" and finds all posts with that tag, it will only return one post, since the other tag has a different ID and is not really associated. Should I be somehow restricting the addition of new tags, or should I be manipulating the way I pull all relevant tags differently?

Here is the controller action which is supposed to grab all relevant posts based on the parameter 'tag':

def indexTagPosts

        @tag = Tag.find(params[:tag])
        @posts = @tag.posts.all

end

And here is the action to save a tag:

def create


    @post = Post.find(params[:post_id])
    @tag = @post.tags.create(params[:tag])

    respond_to do |format|
      if @tag.save
        format.html { redirect_to edit_post_path(@post),:notice => "Success" }
      end
    end  

end

Thanks in advance and apologies for redundancy or bad wording.

Barbiturism answered 11/8, 2011 at 19:4 Comment(0)
M
9

I wish I knew where everyone got the idea to use has_and_belongs_to_many because it's a really difficult thing to manage, even if it seems simple at the start. The better approach is to have a has_many ..., :through type relationship because you can manage the individual links and add meta-data to them easily.

For instance, here is a simple two way join with an intermediate model, a pattern you'll find occurs quite often:

class Post < ActiveRecord::Base
  has_many :post_tags
  has_many :tags, :through => :post_tags
end

class Tag < ActiveRecord::Base
  has_many :post_tags
  has_many :posts, :through => :post_tags
end

class PostTag < ActiveRecord::Base
  belongs_to :post
  belongs_to :tag
end

Adding and removing Tag links at this point is trivial:

@post = Post.find(params[:post_id])

if (params[:tags].present?)
  @post.tags = Tag.where(:name => params[:tags].split(/\s*,\s*/))
else
  @post.tags = [ ]
end

The has_many relationship manager will create, update, or destroy the PostTag association models as required.

Generally you'll evolve the Post model to include a utility method for retrieving and assigning the tags using whatever separator you like:

 class Post
   def tags_used
     self.tags.collect(&:name).join(',')
   end

   def tags_used=(list)
     self.tags = list.present? ? Tag.where(:name => list.split(/\s*,\s*/)) : [ ]
   end
 end
Marysa answered 11/8, 2011 at 19:12 Comment(2)
Thanks for the answer. If I were to try to implement each tag as a separate instance of the Tag model, I'm not sure how I would do that.Barbiturism
Usually the point of having a Tag model is so you can find all the things tagged with it. Having a different Tag for each Post would kind of defeat the purpose.Marysa

© 2022 - 2024 — McMap. All rights reserved.