Best Practice/Associations Question

What is the best way to solve this problem: If you have a model - say Vote, and there are votes for various categories of things such as comments, links, etc: What is the best way to implement this? Is it best to create a model/database for each type of vote (comment_votes, news_votes, etc_votes), tag each vote with a vote_type parameter, or maybe do something with polymorphism. Additionally how does that implementation affect how would you go about saying something like user.comment_votes or user.news_votes the user has_many of each of these types. I hope that makes sense and thanks for your ideas.

John Brunsfeld

When you say “votes for various categories”, if each of your different categories are implemented as models, you can use single table inheritance and polymorphic associations.

So you’d have one table, “votes” and you’d link your models using the following structure:

class Vote < AR::Base

belongs_to :voteable, :polymorphic => true

end

class Comment < AR::Base

has_many : votes, :as => : voteable

end

class Product < AR::Base

has_many : votes, :as => : voteable

end

etc

then you can do comment.votes or product.votes.

Adam

Jables wrote:

What is the best way to solve this problem: If you have a model - say Vote, and there are votes for various categories of things such as comments, links, etc: What is the best way to implement this? Is it best to create a model/database for each type of vote (comment_votes, news_votes, etc_votes), tag each vote with a vote_type parameter, or maybe do something with polymorphism. Additionally how does that implementation affect how would you go about saying something like user.comment_votes or user.news_votes the user has_many of each of these types. I hope that makes sense and thanks for your ideas.

John Brunsfeld

This seems to me to be a little muddy. I suggest that you start with what you are voting on, say an issue or better yet a proposal. Then you have a voter model, and finally a ballot model. So a particular voter's ballot is related to a specific proposal.

Thus:

Class Voter < ActiveRecord::Base

has_many :ballots

Class Proposal < ActiveRecord::Base

has_many :ballots

Class Ballot < ActiveRecord::Base

belongs_to :voter belongs_to :proposal

The voter model/table contains all of your identity and eligibility criteria. The proposal model/table contains all of the data relating to a specific issue, including its type or class if this is a singleton attribute, or you can hang a proposal_classifications model off of proposals if you require N type attributes (likewise comments or notes). The ballot contains the voter's choice on the issue plus whatever ancillary data you desire (date and time cast, method of voting, place of voting, ip address, etc.)

You validate the uniqueness of a ballot (one per voter per proposal) in the Ballot model.

HTH

This is what I was thinking Adam, do you know if this is an efficient way to do this as the votes table starts to grow?

Question to anyone: So if you have a polymorphic relationship such as Adam posted with his setup, you can say comment.votes. How can you go the other way and say "Vote.comment"? Is that a has_one :comment in the Vote model, and if so would you also put has_one :product?

I’m sure splitting it up into separate tables would be more efficient, especially if, for example, you had a class which has very few votes, considering you’d still need to scan through a table with a large number of records to return the given results, versus the smaller table lookup which could be achieved had you separated the votes for this class into another table.

But then it’s much nicer to implement it this way. So it’s a trade off in terms of complexity versus efficiency (isn’t that always the case? ;-). I mean, it’s not terribly complex to implement using separate tables, but STI with polymorphism is cleaner.

I guess you could always start with the STI implementation and then switch to separate tables if you find that it’s too much of a bottleneck.

Adam

to get the object for a vote, you’d use:

vote.voteable

which will return the object associated with the vote (either a comment, product, etc)

Adam