5

I currently have a comment controller that has the method vote_up and vote_down this is how my vote_up currently works.

My Comment model has description and a count field.

  def vote_up
    @comment = Comment.find(params[:comment_id])
    @comment.count += 1
    if @comment.save
      flash[:notice] = "Thank you for voting"
      respond_to do |format|
        format.html { redirect_to show_question_path(@comment.question) }
        format.js
      end
    else
      flash[:notice] = "Error Voting Please Try Again"
      redirect_to show_question_path(@comment.question)
    end
  end

This allows for multiple vote up and downs. How would I design it so that a user can only vote once per comment but somehow keep track if they voted up or down, so they have the ability to change their vote if they want too.

Kevin
  • 625
  • 2
  • 11
  • 19
  • note too that there are many results found from searching for 'one vote per user' on SO – KevinDTimm Jul 21 '11 at 13:43
  • You'll need another model to track votes. You can use uniqueness constraints to only allow one vote per user, which is exactly what Mikhailov's answer does. – Wizard of Ogz Jul 21 '11 at 14:08
  • I was reading over that answer but how would I make it so that a user is allowed to change his vote later on? Lets say from -1 to 1 – Kevin Jul 21 '11 at 14:11

3 Answers3

3

You could do something like this. It prohibits identical votes but allows changing the vote to the opposite (it's a thumbs up/thumbs down system).

def vote(value, user) # this goes to your model

  #find vote for this instance by the given user OR create a new one
  vote = votes.where(:user_id => user).first || votes.build(:user_id => user)

  if value == :for
    vote_value = 1
  elsif value == :against
    vote_value = -1
  end

  if vote.value != vote_value
    vote.value = vote_value
    vote.save
  end
end

migration:

def self.up
    create_table :votes do |t|
    t.references :comment, :null => false
    t.references :user, :null => false
    t.integer :value, :null => false
  end
  add_index :votes, :post_id
  add_index :votes, :user_id
  add_index :votes, [:post_id, :user_id], :unique => true
end

Alternatively, you could use a gem called thumbs_up or any other.

bassneck
  • 4,053
  • 4
  • 24
  • 32
  • Thanks for this I used this method with some other modifications that I got from @mikhailov and I got it to finally work – Kevin Jul 22 '11 at 02:49
2
class AnswersController < ApplicationsController
  def vote
    #params[:answer_id][:vote]
    #it can be "1" or "-1"
    @answer = Answer.find(params[:answer_id])
    @answer.vote!(params[:answer_id][:vote])
  end

  def show
    @answer = Answer.find(params[:answer_id])
    @answer.votes.total_sum
  end

end

class Answer < ActiveRecord::Base
  has_many :votes do
    def total_sum
      votes.sum(:vote)
    end
  end


  def vote!(t)
    self.votes.create(:vote => t.to_i)
  end

end

class Vote < ActiveRecord::Base
  belongs_to :answer
  belongs_to :user

  validates_uniqueness_of :user_id, :scope => :answer_id
end
Anatoly
  • 15,298
  • 5
  • 53
  • 77
  • Yea I figured I needed another model for the votes but how do I implement a way to allow a user to change his vote later one? – Kevin Jul 21 '11 at 14:12
  • **update** action will find vote by answer and user id-s, then update his vote – Anatoly Jul 21 '11 at 14:30
  • You need to blow this up a little bit: Have a migration and model file for your votes. Attributes: [id], user_id:integer, comment_id:integer, is_up:boolean; then you can have a scope (or class function) in your Vote model to return the number of up-/down-votes for a given comment. More importantly: you can keep track of which user voted which comment - and let users change their votes! – emrass Jul 21 '11 at 16:05
  • this is a good point. I'd also suggest to keep total sum as a counter in the parent model – Anatoly Jul 21 '11 at 16:12
  • Thanks I was able to use this to see how it works and modify some of my other code that I had in place. – Kevin Jul 22 '11 at 02:49
1

you could perhaps add a validation in your model to make sure that count is numerically equal to or less than 1

validates :count, :numericality => { :less_than_or_equal_to => 1 }
stephenmurdoch
  • 34,024
  • 29
  • 114
  • 189