Question

I am new to ruby and ruby-on-rails. I need to implement complicated tagging engine:

  • each tag could have 0 or more synonyms (yep, just like on SO)

  • there should be an hierarchy of tags: i.e. there are sub-tags and super-tags. Say, I have three tags: programming, ruby and c. Then, programming is a super-tag of both ruby and c, and when I enter tags for item about ruby, it's not necessary to enter tag programming, I can only tag it with ruby. When I enter tags, all the super-tags should be added recursively. (actually it's strange I've never seen any tags engine with this feature, I really feel lack of it)

  • each user should have its own set of tags, i.e. each Tag belongs_to User. Tags aren't public to anyone, they are private to its owner.

It seems I can't use acts_as_taggable_on gem, right? So I have to implement my own one. Actually I have already implemented Tag class that supports hierarchy, synonyms and users stuff, it seems to work. But there are a lot of questions how to make something taggable with it.

I am trying to reverse-engineer acts_as_taggable_on, it is really complicated for a newbie like me..

So, there are a lot of questions, but I'm going to be more specific now:

Assume I have a class Item that I want to be taggable. As far as I understand, I should use has_many :through association, both on Item and Tag side. And therefore create intermediate table with item_id and tag_id fields.

BUT: I want my tagging engine to be universal! So I don't want to add to Tag model anything related to Item.

So, the only real question is: is it generally ok to create has_many :through association only on Item side?

And secondly, if anyone has some suggestions on the stuff I explained, I'd be happy to see them.

Was it helpful?

Solution 2

Further to swapnilabnave's great answer, I'll explain what you're looking for:

I would recommend breaking up your problem into more modular issues

Your question is whether you can use has_many :through for the relation. The answer, as explained, is "yes", but that won't solve the problem in its entirety


Hierarchy Of Tags

As mentioned, your tags will have "parents" and other items

Although I don't have huge experience with this in Rails, in CakePHP, this will be known as a tree structure. CakePHP handles this by having 3 columns - parent_id, left, right. These basically allow the app to specifically show the various items in your tree based on which numbers are used in those 3 columns

In your case, I'd recommend putting a parent_id column in the tags database, which you'll be able to assign a super tag to if required (it seems you only have one super-tag per tag?). You could handle this in your model using the self-referencing association technique that swapnilabnave posted to give the ability to call the super_tag like this:

@tag.super_tag 

And important note would be that this would only work if you could only have one super_tag per tag. If you wanted an unlimited number of super tags per tag, you'd do this:

class Tag < ActiveRecord::Base
  has_many :sub_tags, class_name: "Tag", :foreign_key => "super_tag_id"

  has_many :tag_super_tags, class_name: "TagSuperTag", :foreign_key => "super_tag_id"
  has_many :super_tags, :through => :tag_super_tags
end

Class TagSuperTag
    belongs_to :tag
    belongs_to :super_tag, :class => "Tag"
end

Although I'm not sure if the join model code will work, it will hopefully show you the idea here. This would create a self-referencing join model called TagSuperTag, and allow you to have a table like this:

tag_super_tags
id | tag_id | super_tag_id | created_at | updated_at

tags (no need for super_tag_id)
id | user_id | name | etc 

This will allow you to add as many super tags as you wish to each tag


User Has Many Tags

Because each user will have many tags, you can just use the standard has_many association, like this:

class User < ActiveRecord::Base
  has_many :tags
end

class Tag < ActiveRecord::Base
 belongs_to :user
end

This means that each tag will have to have a user_id column, to act as a foreign_key


Tags Are Universal

Making tags universal (not just for item) will require a polymorphic association, and what seems to be a join model

As swapnilabnave has described, this join model can be accessed by any model which wants to use it to store tags, thus allowing you to associate any model with it. Here's how you could do this:

class TaggedItem < ActiveRecord::Base
    belongs_to :taggable, polymorphic: true
    belongs_to :tag
end

tagged_items
id | taggable_type | taggable_id | tag_id | created_at | updated_at

This will allow you to reference this model from any other, like this:

class Post < ActiveRecord::Base
    has_many :tagged_items, :class_name => "TaggedItem", :as => :taggable, :dependent => :destroy
    has_many :tags, :through => :tagged_items
end

class Email < ActiveRecord::Base
    has_many :tagged_items, :class_name => "TaggedItem", :as => :taggable, :dependent => :destroy
    has_many :tags, :through => :tagged_items
end

And the Tag model should has_many :tagged_items, too:

class Tag < ActiveRecord::Base
    has_many :tagged_items, :class_name => "TaggedItem", :foreign_key => "tag_id", :dependent => :destroy
end

Hope this helps

OTHER TIPS

I can suggest you few things that might help some/most of your problem statements.

there should be an hierarchy of tags

self referential has_many association can be used Creating a model that has a tree structure

class Tag < ActiveRecord::Base
  has_many :sub_tags, class_name: "Tag", :foreign_key => "super_tag_id"
  belongs_to :super_tag, class_name: "Tag"
end

I want my tagging engine to be universal! So I don't want to add to Tag model anything related to Item

Tagging should be polymorphic, as its widely applicable as used.

class TaggedItem < ActiveRecord::Base
  belongs_to :tag
  belongs_to :taggable, polymorphic: true
end

is it generally ok to create has_many :through association only on Item side?

I think yes, has_may through is the way to go. So your Item model may look like

class Item < ActiveRecord::Base
  has_many :tags, as: :taggable, through: :tagged_items
end
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top