From 36e452437e3ba6c651a3bd195d006f9dcb6e7b69 Mon Sep 17 00:00:00 2001 From: decabeza Date: Fri, 2 Sep 2022 16:41:17 +0200 Subject: [PATCH] Add questions with mutiple answers to polls public interface The `reload` method added to max_votes validation is needed because the author gets here with some changes because of the around_action `switch_locale`, which adds some changes to the current user record and therefore, the lock method raises an exception when trying to lock it requiring us to save or discard those record changes. --- .../questions/answers_component.html.erb | 3 +- .../polls/questions/answers_component.rb | 4 ++ .../questions/question_component.html.erb | 6 ++ app/controllers/polls/questions_controller.rb | 6 +- app/models/concerns/questionable.rb | 17 +++++ app/models/poll/answer.rb | 1 + .../{answer.js.erb => answers.js.erb} | 0 config/locales/en/general.yml | 3 + config/locales/es/general.yml | 3 + .../polls/questions/answers_component_spec.rb | 14 ++++ spec/system/polls/votation_types_spec.rb | 69 +++++++++++++++++++ 11 files changed, 121 insertions(+), 5 deletions(-) rename app/views/polls/questions/{answer.js.erb => answers.js.erb} (100%) create mode 100644 spec/system/polls/votation_types_spec.rb diff --git a/app/components/polls/questions/answers_component.html.erb b/app/components/polls/questions/answers_component.html.erb index de84bcd7f..c73247ec2 100644 --- a/app/components/polls/questions/answers_component.html.erb +++ b/app/components/polls/questions/answers_component.html.erb @@ -15,7 +15,8 @@ remote: true, title: t("poll_questions.show.vote_answer", answer: question_answer.title), class: "button secondary hollow", - "aria-pressed": false do %> + "aria-pressed": false, + disabled: disable_answer?(question_answer) do %> <%= question_answer.title %> <% end %> <% end %> diff --git a/app/components/polls/questions/answers_component.rb b/app/components/polls/questions/answers_component.rb index b21459a97..0509d8fa7 100644 --- a/app/components/polls/questions/answers_component.rb +++ b/app/components/polls/questions/answers_component.rb @@ -18,6 +18,10 @@ class Polls::Questions::AnswersComponent < ApplicationComponent user_answers.find_by(answer: question_answer.title) end + def disable_answer?(question_answer) + question.multiple? && user_answers.count == question.max_votes + end + private def user_answers diff --git a/app/components/polls/questions/question_component.html.erb b/app/components/polls/questions/question_component.html.erb index 5fd7eeb8b..3cd99a1b4 100644 --- a/app/components/polls/questions/question_component.html.erb +++ b/app/components/polls/questions/question_component.html.erb @@ -3,6 +3,12 @@ <%= question.title %> + <% if question.votation_type.present? %> + + <%= t("poll_questions.description.#{question.vote_type}", maximum: question.max_votes) %> + + <% end %> +
<%= render Polls::Questions::AnswersComponent.new(question) %>
diff --git a/app/controllers/polls/questions_controller.rb b/app/controllers/polls/questions_controller.rb index 3731aa2f9..a21acc9bc 100644 --- a/app/controllers/polls/questions_controller.rb +++ b/app/controllers/polls/questions_controller.rb @@ -5,9 +5,7 @@ class Polls::QuestionsController < ApplicationController has_orders %w[most_voted newest oldest], only: :show def answer - answer = @question.answers.find_or_initialize_by(author: current_user) - - answer.answer = params[:answer] + answer = @question.find_or_initialize_user_answer(current_user, params[:answer]) answer.save_and_record_voter_participation respond_to do |format| @@ -15,7 +13,7 @@ class Polls::QuestionsController < ApplicationController redirect_to request.referer end format.js do - render :answer + render :answers end end end diff --git a/app/models/concerns/questionable.rb b/app/models/concerns/questionable.rb index f7b20b8f4..1d0606fc6 100644 --- a/app/models/concerns/questionable.rb +++ b/app/models/concerns/questionable.rb @@ -10,4 +10,21 @@ module Questionable def unique? votation_type.nil? || votation_type.unique? end + + def find_or_initialize_user_answer(user, title) + answer = answers.find_or_initialize_by(find_by_attributes(user, title)) + answer.answer = title + answer + end + + private + + def find_by_attributes(user, title) + case vote_type + when "unique", nil + { author: user } + when "multiple" + { author: user, answer: title } + end + end end diff --git a/app/models/poll/answer.rb b/app/models/poll/answer.rb index 41d76e925..d9d86e9d6 100644 --- a/app/models/poll/answer.rb +++ b/app/models/poll/answer.rb @@ -38,6 +38,7 @@ class Poll::Answer < ApplicationRecord def max_votes return if !question || question&.unique? || persisted? + author.reload author.lock! if question.answers.by_author(author).count >= question.max_votes diff --git a/app/views/polls/questions/answer.js.erb b/app/views/polls/questions/answers.js.erb similarity index 100% rename from app/views/polls/questions/answer.js.erb rename to app/views/polls/questions/answers.js.erb diff --git a/config/locales/en/general.yml b/config/locales/en/general.yml index 4a70c1a3e..afc277ef4 100644 --- a/config/locales/en/general.yml +++ b/config/locales/en/general.yml @@ -636,6 +636,9 @@ en: show: vote_answer: "Vote %{answer}" voted: "You have voted %{answer}" + description: + unique: "You can select a maximum of 1 answer." + multiple: "You can select a maximum of %{maximum} answers." proposal_notifications: new: title: "Send notification" diff --git a/config/locales/es/general.yml b/config/locales/es/general.yml index d731f4bd4..382b302e6 100644 --- a/config/locales/es/general.yml +++ b/config/locales/es/general.yml @@ -636,6 +636,9 @@ es: show: vote_answer: "Votar %{answer}" voted: "Has votado %{answer}" + description: + unique: "Puedes seleccionar un máximo de 1 respuesta." + multiple: "Puedes seleccionar un máximo de %{maximum} respuestas." proposal_notifications: new: title: "Enviar notificación" diff --git a/spec/components/polls/questions/answers_component_spec.rb b/spec/components/polls/questions/answers_component_spec.rb index 0b05953c2..a5460b83e 100644 --- a/spec/components/polls/questions/answers_component_spec.rb +++ b/spec/components/polls/questions/answers_component_spec.rb @@ -33,6 +33,20 @@ describe Polls::Questions::AnswersComponent do expect(page).to have_css "button[aria-pressed='true']", text: "Yes" end + it "renders disabled buttons when max votes is reached" do + user = create(:user, :verified) + question = create(:poll_question_multiple, :abc, max_votes: 2, author: user) + create(:poll_answer, author: user, question: question, answer: "Answer A") + create(:poll_answer, author: user, question: question, answer: "Answer C") + sign_in(user) + + render_inline Polls::Questions::AnswersComponent.new(question) + + expect(page).to have_button "You have voted Answer A" + expect(page).to have_button "Vote Answer B", disabled: true + expect(page).to have_button "You have voted Answer C" + end + it "when user is not signed in, renders answers links pointing to user sign in path" do render_inline Polls::Questions::AnswersComponent.new(question) diff --git a/spec/system/polls/votation_types_spec.rb b/spec/system/polls/votation_types_spec.rb new file mode 100644 index 000000000..51efb7f2c --- /dev/null +++ b/spec/system/polls/votation_types_spec.rb @@ -0,0 +1,69 @@ +require "rails_helper" + +describe "Poll Votation Type" do + let(:author) { create(:user, :level_two) } + + before do + login_as(author) + end + + scenario "Unique answer" do + question = create(:poll_question_unique, :yes_no) + + visit poll_path(question.poll) + + expect(page).to have_content "You can select a maximum of 1 answer." + expect(page).to have_content(question.title) + expect(page).to have_button("Vote Yes") + expect(page).to have_button("Vote No") + + within "#poll_question_#{question.id}_answers" do + click_button "Yes" + + expect(page).to have_button("You have voted Yes") + expect(page).to have_button("Vote No") + + click_button "No" + + expect(page).to have_button("Vote Yes") + expect(page).to have_button("You have voted No") + end + end + + scenario "Multiple answers" do + question = create(:poll_question_multiple, :abc, max_votes: 2) + visit poll_path(question.poll) + + expect(page).to have_content "You can select a maximum of 2 answers." + expect(page).to have_content(question.title) + expect(page).to have_button("Vote Answer A") + expect(page).to have_button("Vote Answer B") + expect(page).to have_button("Vote Answer C") + + within "#poll_question_#{question.id}_answers" do + click_button "Vote Answer A" + + expect(page).to have_button("You have voted Answer A") + + click_button "Vote Answer C" + + expect(page).to have_button("You have voted Answer C") + expect(page).to have_button("Vote Answer B", disabled: true) + + click_button "You have voted Answer A" + + expect(page).to have_button("Vote Answer A") + expect(page).to have_button("Vote Answer B") + + click_button "You have voted Answer C" + + expect(page).to have_button("Vote Answer C") + + click_button "Vote Answer B" + + expect(page).to have_button("You have voted Answer B") + expect(page).to have_button("Vote Answer A") + expect(page).to have_button("Vote Answer C") + end + end +end