[v] Voting mechanism 投票機制

假設我們今天有一個網站,上面有 pins, 有 users, users 可以對 pins 投票,但每個 user 只能對每個 pin 投一個 upvote, 並且可以取消 upvote, 這個機制要怎麼設計呢?

首先,我們需要一個 Vote model, 而它是做為 join model 來連結 users 和 pins, 或者說,紀錄哪一位 user 投過哪一個 pin.

$ rails g model vote user_id: integer pin_id: integer
$ rake db:migrate

接著,我們要來建立各個資料庫之間的關聯:

models/pin.rb

class Pin < ActiveRecord::Base

  has_many :votes, dependent: :destroy
  has_many :upvoted_users, through: :votes, source: :user

  ...

end

models/user.rb

class User < ActiveRecord::Base

  has_many :votes, dependent: :destroy
  has_many :upvoted_pins, through: :votes, source: :pin

  ...

end

model/vote.rb

class Vote < ActiveRecord::Base

  belongs_to :user
  belongs_to :pin
  validates_uniqueness_of :pin_id, scope: :user_id

end

最後,我們在 controller 裡面寫上 actions.

controllers/ideas_controller.rb

def upvote
  @pin = Pin.find(params[:id])
  @pin.votes.create(user_id: current_user.id)
  
  if @pin.save
    flash[:notice] =  "Thank you for upvoting!"
    redirect_to :back
  else 
    flash[:notice] =  "You have already upvoted this!"
    redirect_to :back
  end
end

def cancel_upvote
  @pin = Pin.find(params[:id])
  @vote = Vote.find_by(user_id: current_user.id, pin_id: @pin.id)

  if @vote
    @vote.destroy
    flash[:notice] = "You've cancelled your upvote."
    redirect_to :back
  else
    flash[:warning] = "You have no upvotes to cancel."
    redirect_to :back
  end
end

這樣,我們就完成了這個投票機制。

也可以用同樣的概念讓每一個 user 可以對每一個 pin 投下一個 downvote, 並且也可以取消 downvote.

http://stackoverflow.com/questions/24596651/rails-allow-users-to-upvote-only-once-a-pin