Rails: has_many :through association only at one side - ruby-on-rails

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.

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

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

Related

how to handle "synonymns" in a has_many relationship

This is a bit of a sticky issue. I have a Rails association but need to add functionality to handle some special cases.
class Item < ActiveRecord::Base
has_many :notes
end
class Note < ActiveRecord::Base
belongs_to :item
end
The issue is that you could have something like this in db (identical items that might have a difference in sizing or quantity but are part of the desc and can't really be broken out due to accounting software):
items
id desc
1 a glass something
..
10 a bottle of something
..
20 a case of bottles of something
notes
id note item_id
3 "a note about something" 1
What I want to do is create a linke so that items 1,10, and 20, when they load notes, will all load notes with an id of 1
For example:
item1=Item.find(1)
item1.notes[0].id # 3
item10=Item.find(10)
item10.notes[0].id # 3
item20=Item.find(20)
item20.notes[0].id # 3
I feel like there should be a really basic way of doing this and was looking for suggestions. This is hacky but might work would be to write a dash separated list of other_ids into the notes table so that a notes.other_ids="-10--20-"
class Item < ActiveRecord::Base
has_many :notes
def sym_items
Note.where('other_ids like ?',"%-#{self.id}-%")
end
end
We really would only have to deal with this in a single scenario so a hacky sol'n woudl be ok but obviously would like better. Possibly do a has_many :through. I'm not sure about the latter - perhaps too much added complexity. Any help or advice would be appreciated.
thx
So I'm not sure I fully understand the situation. It seems like you might want something like this:
class Item < ActiveRecord::Base
has_many :notes
has_many :other_item_notes
has_many :other_notes, class_name: "Note", through: :other_item_notes, source: :note
has_many :other_items, class_name: "Item", through: :notes, source: :other_items
end
class Note < ActiveRecord::Base
belongs_to :item
has_many :other_item_notes
has_many :other_items, class_name: "Item", through: :other_item_notes, source: :item
end
class OtherItemNote < ActiveRecord::Base
belongs_to :item
belongs_to :note
end
The trick here is that you have a many-to-many association between items and notes (I assume you know how/why those other associations are supposed to work). Then we use the associations of those other items that we can now access to access their associated items. There may be complications with N+1 queries and such, but some of that can be dealt with by using inverse_of on your associations.
If you do it this way, you are querying against indexed id columns in your database searches, rather than a string comparison query like in your example above.

Rails model advice

I need some suggests for the model relationship that I'm going to develop:
I want to create a research table that keep track of all the reasearches performed inside the platform.
In the platform I'm developing, an user can search for other users.
In order to do it, I thought to create 3 fields in the research table: Performer (user_id that submit the research) Research_string (The string that the performer submit) and Results (that is one or more user).
Of course the relationship among user and research tables will be many to many, but note that the research table has 2 different column that involves the user_id (performer and results) so how can I specify to rails ? I thought something like that :
class User < ActiveRecord::Base
has_many :performed_research, :through => :research_table
class Research < ActiveRecord::Base
has_many :users
But how can I specify that the has_many users in the Research tables implies two different relations depending by the column ?
Tnx
EDITED: my solution
Your solution is not correct, because the user has only the research performed and not the research where he is resulted. I made another solution, that it's not the best of clear but it works, I would like to have your judge:
class Research < ActiveRecord::Base
belongs_to :searcher, :class_name => 'User', :foreign_key=> 'submitter_id'
has_many :found_users, :through=>:user_researches,:source=>:user
#It is necessary to let the association work without class_name it would look for
userResearch class
has_many :user_researches, :class_name =>'User_research'
end
class User_research < ActiveRecord::Base
belongs_to :user
belongs_to :research
end
class User < ActiveRecord::Base
# It returns a list of research performed by the user
has_many :researches, :foreign_key => 'submitter_id'
# It is necessary to let the searcher relationship works
has_many :user_researches, :class_name =>'User_research'
#==> Searcher will return an Array of research where the user's skill has been researched
has_many :follower_researches, :through => :user_researches, :source=>:research
end
I say that it;s not the best because the follower_research of the user model, show an array of research when he has been results ... and not an Array of submitter that searching him, so to obtain them, I have to scroll the array of research and then take the searcher field ... Are you able to perform an improvement (hopefully less complex than this)
Do you mean something like this?
class User < ActiveRecord::Base
has_many :research_entries
class ResearchEntry < ActiveRecord::Base
has_one :performer, :class_name => 'User'
has_and_belongs_to_many :resulting_users, :class_name => 'User'
Update: I changed this to use the has_and_belongs_to_many relationship which will allow you to use a join table to connect many users into "resulting_users" field of the ResearchEntry
Something like this:
class User < ActiveRecord::Base
has_many :submitted_researches, :class_name => 'Research', :foreign_key => 'submitter_id'
has_manu :researches, :through => :performed_researches
class Research < ActiveRecord::Base
belongs_to :submitter, :class_name => 'User'
has_many :researchers, :through => :performed_researches, :class_name => 'User'
class PerformedResearch < ActiveRecord::Base
belongs_to :user
belongs_to :research
See: http://guides.rubyonrails.org/association_basics.html#the-has_many-through-association
Update: I think I misread something. What is results? and how is it related to users?
Update 2: Still not sure I understand but here's another stab
I originally thought you were doing something related to academic research and the Research model was for research papers with the performer being scientists that did the research.
From re-reading your question I'm now thinking you have a rails application with some users and a search feature that lets a user search for other users and for each search you are trying to keep track of which user did the search and which users they found.
Let me know if any of these assumptions are wrong.
Based on that:
class User < ActiveRecord::Base
has_many :searches #all the searches this user performed
class Search < ActiveRecord::Base
belongs_to :searcher, :class_name => 'User' # the user who performed the search
has_many :found_users, :though => :user_search_results, :foreign_key => 'user_id' # using foreign_key because I want to change the name to 'found_users'
#alternatively if you don't want to use foreign_key use the line bellow instead
#has_many :users, :thorough => :user_search_results
class UserSearchResult < ActiveRecord::Base
belongs_to :user
belongs_to :search

Setting a type for one side of a many-to-many association with a join model

I set up a public github app (see: https://github.com/greenplastik/testapp to download) to work through a problem I'm having with specifying a type on one side of a many-to-many association between two models, via a join model.
Given Person and Book models and a Book_Person join model, I want to be able to do the following:
#book = Book.first
#book.people # lists people for book
#book.authors # lists author-type people for book
#book.editors # lists editor-type people for book
and
#person = Person.first
#person.books # lists books for people
This app was set up in part using the instructions found through Google. There's a link to those instructions in the README of my testapp.
I tried, as best I could, to remove the inconsistencies and typos. I can't get it to work.
Any help would be appreciated. I've included the sqlite database for easier testing on your end.
I'm sorry i haven't got time to post neither a full solution or a tested one, but this should at least put you on the right track..
The best way to achieve what you're looking for is with Single Table Inheritance used with a polymorphic relationship.
I'd suggest redefining Author and Editor to be subclasses of Person
As in
class Person < ActiveRecord::Base
has_many :book_people
has_many :books, :through => :book_people
end
class Author < Person
end
class Editor < Person
end
class BookPerson < ActiveRecord::Base
belongs_to :person, :polymorphic => true
belongs_to :book
end
class Book < ActiveRecord::Base
has_many :book_people
has_many :people, :through => :book_people
has_many :authors, :through => :book_people, :source => :person, :source_type => "Author"
has_many :editors, :through => :book_people, :source => :person, :source_type => "Editor"
end
However this would be suboptimal if a single person could fill all three roles. There's probably a way around that by using the same STI name for the three of them. But then you'd make it harder to query for all authors.

Rails modeling for a user

When building a rails app that allows a User to login and create data, is it best to setup a belongs_to :user association on every single model? For example, let's say a user can create Favorites, Colors and Tags.
And let's say Favorites has_many :tags and Colors also has_many :tags. Is it still important for Tags to belong_to :user assuming the User is the only person who has authority to edit those tags?
And a similar question along the same lines: When updating data in FavoritesController, I've come to the conclusion that you perform CRUD operations by always doing something like current_user.favorites.find(param[:id].update_attributes(param[:favorite]) so that they can definitely only update models that belong to them. Right?
Update Wasn't too happy with any of the answers, as no one really answered my question but instead went after the for-example-only Tags model suggesting better ways to do that. I'm assuming I was right, and models should belong_to :user. I also discovered some great security tips that address my questions here: http://asciicasts.com/episodes/178-seven-security-tips
As you describe the tags it seems that they are more of an aspect, so you can implement them as a polymorphic association. But you should do it many-to-many, as tags can be reused among users and taggable objects. Let's call the join model Tagging, which will be the one that belongs to user if you want to remember who created the tagging.
class Tag < ActiveRecord::Base
has_many :taggings, :dependent => :destroy
has_many :colors, :through => :taggings, :source => :taggable, :source_type => "Color"
has_many :favorites, :through => :taggings, :source => :taggable, :source_type => "Favorite"
end
class Tagging < ActiveRecord::Base
belongs_to :user
belongs_to :taggable, :polymorphic => true
belongs_to :tag
end
class Color < ActiveRecord::Base
belongs_to :user
has_many :taggings, :as => :taggable
has_many :tags, :through => :taggings
end
class Favorite < ActiveRecord::Base
belongs_to :user
has_many :taggings, :as => :taggable
has_many :tags, :through => :taggings
end
class User < ActiveRecord::Base
has_many :favorites
has_many :colors
has_many :taggings
has_many :tags, :through => :taggings
end
As for the Favorite updating, I agree with you: you will mostly work within the scope of a user (most likely the currently logged in user).
It depends on your model. Both cases are valid but I'd discorage making a circular relationships like that. Having a hierarchy is more flexible. For example: User->Favorites->Tags (unless you want to tag users as well)
User.favorites.find(params[:id]).update_attributes(param[:favorite])
is what you mean I guess (syntax). Whoever calls the URL will perform that action. Dont rely on the fact that that URL is visible to one user only (owner of the favorite). You should have checks in place that the currently logged in user is the only one performing actions on the objects that belong to him.
The proposed mechanism sounds a bit too complex for me. I prefer the current_user way. Assume there is a current_user (following the authlogic way) in your authentication system, then simple add a user references (user_id) in every relevant table. Update the current_user for new or update record via a controller filter.
In the models, put relevant belongs_to :users accordingly, put enough has_many in users model if needed.
:has_many and :belongs_to in AR will explains the relationship between models, but not necessarily you have to use them in your models, the associaton between them will be already present in the tables as a foreign key.
But adding :has_many or :belongs_to to your models will give you extra methods to your model
ex:
class User < ActiveRecord::Base
has_many :favorites
#def favorites
# Favorite.find_all_by_user_id(self.id)
# end
end
If you mention has_many it will give a new method in your model called favorites, that method will be invisible (will be present in the AR).
Similarly for any association, if you are planning to use this kind of methods you should use associations in your models.

Rails ActiveRecord relationships - has many and belongs to associations

I've created 3 models:
Article: contains an article
Tag: contains tags
ArticleTag: meant for associating a many-to-one tags to article relationship. It contains a tag_id and an article_id.
The problem I'm having is I'm fairly new to the active record technology and I don't understand the proper way to define everything. Currently, which I think is wrong, is I have a
ArticleTag
belongs_to :article
belongs_to :tag
Now, from here my thought was to then add
Article
:has_many :tag
I'm not sure if im approaching this correctly at all. Thanks for the help!
It depends whether you want a join model or not. A join model lets you hold extra information against the association between two other models. For example, perhaps you want to record a timestamp of when the article was tagged. That information would be recorded against the join model.
If you don't want a join model, then you could use a simple has_and_belongs_to_many association:
class Article < ActiveRecord::Base
has_and_belongs_to_many :tags
end
class Tag < ActiveRecord::Base
has_and_belongs_to_many :articles
end
With a Tagging join model (which is a better name than ArticleTag), it would look like this:
class Article < ActiveRecord::Base
has_many :taggings
has_many :tags, :through => :taggings
end
class Tag < ActiveRecord::Base
has_many :taggings
has_many :articles, :through => :taggings
end
class Tagging < ActiveRecord::Base
belongs_to :article
belongs_to :tag
end
A Guide to Active Record Associations
You should use has_many when the relationship is one-way. An article has many tags, but tags also have many articles, so that's not quite right. A better choice might be has_and_belongs_to_many: an Article has many Tags, and those Tags also reference articles. A belongs_to B means that A references B; A has_one B means that B references A.
Here's a summary of the relationships you might see:
Article
has_and_belongs_to_many :tags # An article has many tags, and those tags are on
# many articles.
has_one :author # An article has one author.
has_many :images # Images only belong to one article.
belongs_to :blog # This article belongs to one blog. (We don't know
# just from looking at this if a blog also has
# one article or many.)
Off the top of my head, Article should be:
has_many :article_tags
has_many :tags, :through => :article_tags

Resources