0

I've implemented an up/down voting system that updates with ajax in my Ruby on Rails app. The buttons call the create method, a vote is inserted into the database, and the vote sum is calculated.

However, as of now, a user can upvote or downvote as many times as he or she would like. I want the voting to be like what we see here on StackOverflow, where a user can only vote up or down once, and the votes can be undone. How do I construct the logic for this?

Justin Meltzer
  • 13,318
  • 32
  • 117
  • 182

3 Answers3

1

I'd recommend the acts_as_rateable gem, it's what I've been using for this sort of requirement for multiple websites. Does the job perfectly.

If you'd rather implement this yourself, your Rating model should have a user_id and be polymorphic so as to attach itself to whatever models you'd like to rate. Then you can simply code your AJAX controller to reject duplicate votes. At the front end, Javascript that removes the link functionality from the existing upvote/downvote should be implemented for good UX.

Fareesh Vijayarangam
  • 5,037
  • 4
  • 23
  • 18
  • right now it's an independent resource that has both a video_id and a user_id – Justin Meltzer Mar 18 '11 at 18:25
  • If videos are the only thing you're going to be rating, then that's an alright approach. In this case, you can probably just do `@previous_rating = Rating.where(:video_id => params[:video_id], :user_id => current_user.id).first` and check `@previous_rating.nil?` to check if the user has rated this video before. If he has, check the incoming rating and change it to an upvote/downvote if it's different, or return if it's the same. – Fareesh Vijayarangam Mar 18 '11 at 18:29
  • This makes sense. Why do you have to do `.first` at the end of the `where` call? Shouldn't the user have only one vote anyway? – Justin Meltzer Mar 18 '11 at 18:35
0

I think you have a little more work to do. You should add to your model a many to many relationship between questions and users, in which you remember which user voted for which question and in which way(up or down). After that, when you render the question on the screen, you should select from that table the votes of the current user related to the question displayed.

When a user votes for a question you should add a record to the corresponding table.Also, when displaying the question, If the user has upvoted(downvoted), then you should style the upvote(downvote) link acoordingly. And if a user tries to vote again, you can check it before registering the vote in the database if the user already voted.

Maybe this is not the fastest way to do this kind of thing, but it gives you the most control (you can later display a detailed statistic about the voting style of a certain user, date and time at which the vote was casted, you can impose a limit to the number of votes a user can cast per day and so on...)

Victor Blaga
  • 1,822
  • 4
  • 19
  • 28
0
class Vote < ActiveRecord::Base
  belongs_to :question
  belongs_to :user

  validates_uniqueness_of :user_id, :scope=>question_id    
end

http://ar.rubyonrails.org/classes/ActiveRecord/Validations/ClassMethods.html#M000086

Your controller will return errors if you attempt to vote more than once. Have the UI delete the vote if you down a vote you've voted for.

Jesse Wolgamott
  • 40,197
  • 4
  • 83
  • 109
  • If the user upvotes a second time, the vote should be undone, so I don't think this is what I'm looking for. – Justin Meltzer Mar 18 '11 at 18:34
  • More specifically, your UI should send a method=>:delete if the vote exists. That's the SO way. – Jesse Wolgamott Mar 18 '11 at 18:37
  • oh I see what you're saying... always delete the old vote if the user tries to vote again, but only add a new vote if it's a different vote direction (up vs. down) – Justin Meltzer Mar 18 '11 at 18:38
  • But how would I get around the validation if the user wants to switch from an up vote to a down vote? – Justin Meltzer Mar 18 '11 at 18:39
  • You could first delete all votes cast by the user, then cast the vote. – Jesse Wolgamott Mar 18 '11 at 18:43
  • so if the vote is deleted, the new vote will pass the validation? the old vote with the associated user_id won't be stored somewhere where I don't expect it? because I know there's some difference between `delete` and `destroy`... – Justin Meltzer Mar 18 '11 at 18:47
  • that's correct. the HTTP method is delete, but you would destroy any vote that exists for the user and the question. Something like Vote.delete_all("question_id = ? and user_id = ?, @question.id, current_user.id) – Jesse Wolgamott Mar 18 '11 at 19:01