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