posts: allow toggling between upvotes and downvotes.
Like 9efb374ae, allow users to toggle between upvoting and downvoting a
post without raising an error or having to manually remove the vote
first. If you upvote a post, then downvote it, the upvote is
automatically removed and replaced by the downvote.
Other changes:
* Tagging a post with `upvote:self` or `downvote:self` is now silently
ignored when the user doesn't have permission to vote, instead of
raising an error.
* Undoing a vote that doesn't exist now does nothing instead of
returning an error. This can happen if you open the same post in two
tabs, undo the vote in tab 1, then try to undo the vote again in tab 2.
Changes to the /post_votes API:
* `POST /post_votes` and `DELETE /post_votes` now return a post vote
instead of a post.
* The `score` param in `POST /post_votes` is now 1 or -1, not `up` or
`down`.
This commit is contained in:
@@ -2,9 +2,8 @@ class CommentVote < ApplicationRecord
|
||||
belongs_to :comment
|
||||
belongs_to :user
|
||||
|
||||
validates_presence_of :score
|
||||
validates_uniqueness_of :user_id, :scope => :comment_id, :message => "have already voted for this comment"
|
||||
validates_inclusion_of :score, :in => [-1, 1], :message => "must be 1 or -1"
|
||||
validates :user_id, uniqueness: { scope: :comment_id, message: "have already voted for this comment" }
|
||||
validates :score, inclusion: { in: [-1, 1], message: "must be 1 or -1" }
|
||||
|
||||
after_create :update_score_after_create
|
||||
after_destroy :update_score_after_destroy
|
||||
|
||||
@@ -633,7 +633,8 @@ class Post < ApplicationRecord
|
||||
remove_favorite(CurrentUser.user)
|
||||
|
||||
when /^(up|down)vote:(.+)$/i
|
||||
vote!($1)
|
||||
score = ($1 == "up" ? 1 : -1)
|
||||
vote!(score, CurrentUser.user)
|
||||
|
||||
when /^status:active$/i
|
||||
raise User::PrivilegeError unless CurrentUser.is_approver?
|
||||
@@ -779,8 +780,7 @@ class Post < ApplicationRecord
|
||||
|
||||
def add_favorite!(user)
|
||||
Favorite.add(post: self, user: user)
|
||||
vote!("up", user) if Pundit.policy!(user, PostVote).create?
|
||||
rescue PostVote::Error
|
||||
vote!(1, user)
|
||||
end
|
||||
|
||||
def delete_user_from_fav_string(user_id)
|
||||
@@ -789,8 +789,7 @@ class Post < ApplicationRecord
|
||||
|
||||
def remove_favorite!(user)
|
||||
Favorite.remove(post: self, user: user)
|
||||
unvote!(user) if Pundit.policy!(user, PostVote).create?
|
||||
rescue PostVote::Error
|
||||
unvote!(user)
|
||||
end
|
||||
|
||||
def remove_favorite(user)
|
||||
@@ -871,30 +870,22 @@ class Post < ApplicationRecord
|
||||
end
|
||||
|
||||
module VoteMethods
|
||||
def can_be_voted_by?(user)
|
||||
!PostVote.exists?(:user_id => user.id, :post_id => id)
|
||||
def vote!(score, voter)
|
||||
# Ignore vote if user doesn't have permission to vote.
|
||||
return unless Pundit.policy!(voter, PostVote).create?
|
||||
|
||||
with_lock do
|
||||
votes.destroy_by(user: voter)
|
||||
votes.create!(user: voter, score: score)
|
||||
reload # PostVote.create modifies our score. Reload to get the new score.
|
||||
end
|
||||
end
|
||||
|
||||
def vote!(vote, voter = CurrentUser.user)
|
||||
unless Pundit.policy!(voter, PostVote).create?
|
||||
raise PostVote::Error.new("You do not have permission to vote")
|
||||
end
|
||||
def unvote!(voter)
|
||||
return unless Pundit.policy!(voter, PostVote).create?
|
||||
|
||||
unless can_be_voted_by?(voter)
|
||||
raise PostVote::Error.new("You have already voted for this post")
|
||||
end
|
||||
|
||||
votes.create!(user: voter, vote: vote)
|
||||
reload # PostVote.create modifies our score. Reload to get the new score.
|
||||
end
|
||||
|
||||
def unvote!(voter = CurrentUser.user)
|
||||
if can_be_voted_by?(voter)
|
||||
raise PostVote::Error.new("You have not voted for this post")
|
||||
else
|
||||
votes.where(user: voter).destroy_all
|
||||
reload
|
||||
end
|
||||
votes.destroy_by(user: voter)
|
||||
reload
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -1,15 +1,12 @@
|
||||
class PostVote < ApplicationRecord
|
||||
class Error < StandardError; end
|
||||
|
||||
belongs_to :post
|
||||
belongs_to :user
|
||||
attr_accessor :vote
|
||||
|
||||
after_initialize :initialize_attributes, if: :new_record?
|
||||
validates_presence_of :score
|
||||
validates_inclusion_of :score, in: [1, -1]
|
||||
after_create :update_post_on_create
|
||||
after_destroy :update_post_on_destroy
|
||||
validates :user_id, uniqueness: { scope: :post_id, message: "have already voted for this post" }
|
||||
validates :score, inclusion: { in: [1, -1], message: "must be 1 or -1" }
|
||||
|
||||
after_create :update_score_after_create
|
||||
after_destroy :update_score_after_destroy
|
||||
|
||||
scope :positive, -> { where("post_votes.score > 0") }
|
||||
scope :negative, -> { where("post_votes.score < 0") }
|
||||
@@ -23,16 +20,6 @@ class PostVote < ApplicationRecord
|
||||
q.apply_default_order(params)
|
||||
end
|
||||
|
||||
def initialize_attributes
|
||||
self.user_id ||= CurrentUser.id
|
||||
|
||||
if vote == "up"
|
||||
self.score = 1
|
||||
elsif vote == "down"
|
||||
self.score = -1
|
||||
end
|
||||
end
|
||||
|
||||
def is_positive?
|
||||
score > 0
|
||||
end
|
||||
@@ -41,19 +28,19 @@ class PostVote < ApplicationRecord
|
||||
score < 0
|
||||
end
|
||||
|
||||
def update_post_on_create
|
||||
if score > 0
|
||||
Post.where(:id => post_id).update_all("score = score + #{score}, up_score = up_score + #{score}")
|
||||
def update_score_after_create
|
||||
if is_positive?
|
||||
Post.update_counters(post_id, { score: score, up_score: score })
|
||||
else
|
||||
Post.where(:id => post_id).update_all("score = score + #{score}, down_score = down_score + #{score}")
|
||||
Post.update_counters(post_id, { score: score, down_score: score })
|
||||
end
|
||||
end
|
||||
|
||||
def update_post_on_destroy
|
||||
if score > 0
|
||||
Post.where(:id => post_id).update_all("score = score - #{score}, up_score = up_score - #{score}")
|
||||
def update_score_after_destroy
|
||||
if is_positive?
|
||||
Post.update_counters(post_id, { score: -score, up_score: -score })
|
||||
else
|
||||
Post.where(:id => post_id).update_all("score = score - #{score}, down_score = down_score - #{score}")
|
||||
Post.update_counters(post_id, { score: -score, down_score: -score })
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
Reference in New Issue
Block a user