diff --git a/app/models/poll/answer.rb b/app/models/poll/answer.rb index 685830201..904216355 100644 --- a/app/models/poll/answer.rb +++ b/app/models/poll/answer.rb @@ -7,6 +7,7 @@ class Poll::Answer < ApplicationRecord validates :question, presence: true validates :author, presence: true validates :answer, presence: true + validate :max_votes validates :answer, inclusion: { in: ->(a) { a.question.possible_answers }}, unless: ->(a) { a.question.blank? } @@ -21,4 +22,16 @@ class Poll::Answer < ApplicationRecord Poll::Voter.find_or_create_by!(user: author, poll: poll, origin: "web") end end + + private + + def max_votes + return if !question || question&.unique? || persisted? + + author.lock! + + if question.answers.by_author(author).count >= question.max_votes + errors.add(:answer, "Maximum number of votes per user exceeded") + end + end end diff --git a/spec/models/poll/answer_spec.rb b/spec/models/poll/answer_spec.rb index d420e367e..b2b419c23 100644 --- a/spec/models/poll/answer_spec.rb +++ b/spec/models/poll/answer_spec.rb @@ -23,6 +23,30 @@ describe Poll::Answer do expect(answer).not_to be_valid end + it "is not valid when user already reached multiple answers question max votes" do + author = create(:user) + question = create(:poll_question_multiple, :abc, max_votes: 2) + create(:poll_answer, author: author, question: question, answer: "Answer A") + create(:poll_answer, author: author, question: question, answer: "Answer B") + answer = build(:poll_answer, author: author, question: question, answer: "Answer C") + + expect(answer).not_to be_valid + end + + it "validates max votes when creating answers at the same time", :race_condition do + author = create(:user, :level_two) + question = create(:poll_question_multiple, :abc, max_votes: 2) + create(:poll_answer, question: question, answer: "Answer A", author: author) + answer = build(:poll_answer, question: question, answer: "Answer B", author: author) + other_answer = build(:poll_answer, question: question, answer: "Answer C", author: author) + + [answer, other_answer].map do |a| + Thread.new { a.save } + end.each(&:join) + + expect(Poll::Answer.count).to be 2 + end + it "is valid for answers included in the Poll::Question's question_answers list" do question = create(:poll_question) create(:poll_question_answer, title: "One", question: question)