diff --git a/app/lib/poll_option_finder.rb b/app/lib/poll_option_finder.rb new file mode 100644 index 000000000..37544926c --- /dev/null +++ b/app/lib/poll_option_finder.rb @@ -0,0 +1,31 @@ +class PollOptionFinder + attr_reader :question + + def initialize(question) + @question = question + end + + def manageable_choices + choices_map.select { |choice, ids| ids.count == 1 } + end + + def unmanageable_choices + choices_map.reject { |choice, ids| ids.count == 1 } + end + + private + + def choices_map + @choices_map ||= existing_choices.to_h do |choice| + [choice, options.where("lower(title) = lower(?)", choice).distinct.ids] + end + end + + def options + question.question_options.joins(:translations).reorder(:id) + end + + def existing_choices + question.answers.where(option_id: nil).distinct.pluck(:answer) + end +end diff --git a/lib/tasks/polls.rake b/lib/tasks/polls.rake index 7b9472145..a3ce11e4b 100644 --- a/lib/tasks/polls.rake +++ b/lib/tasks/polls.rake @@ -37,26 +37,35 @@ namespace :polls do logger.info "Removing duplicate answers in polls" Tenant.run_on_each do - duplicate_ids = Poll::Answer.where(option_id: nil) - .select(:question_id, :author_id, :answer) - .group(:question_id, :author_id, :answer) - .having("count(*) > 1") - .pluck(:question_id, :author_id, :answer) + Poll::Question.find_each do |question| + manageable_titles = PollOptionFinder.new(question).manageable_choices.keys - duplicate_ids.each do |question_id, author_id, answer| - poll_answers = Poll::Answer.where(question_id: question_id, author_id: author_id, answer: answer) + question.question_options.each do |option| + titles = option.translations.where(title: manageable_titles).select(:title).distinct - poll_answers.excluding(poll_answers.first).each do |poll_answer| - poll_answer.delete + author_ids = question.answers + .where(answer: titles) + .select(:author_id) + .group(:author_id) + .having("count(*) > 1") + .pluck(:author_id) - tenant_info = " on tenant #{Tenant.current_schema}" unless Tenant.default? - log_message = "Deleted duplicate record with ID #{poll_answer.id} " \ - "from the #{Poll::Answer.table_name} table " \ - "with question_id #{question_id}, " \ - "author_id #{author_id} " \ - "and answer #{answer}" + tenant_info.to_s - logger.info(log_message) - duplicate_records_logger.info(log_message) + author_ids.each do |author_id| + poll_answers = question.answers.where(option_id: nil, answer: titles, author_id: author_id) + + poll_answers.excluding(poll_answers.first).each do |poll_answer| + poll_answer.delete + + tenant_info = " on tenant #{Tenant.current_schema}" unless Tenant.default? + log_message = "Deleted duplicate record with ID #{poll_answer.id} " \ + "from the #{Poll::Answer.table_name} table " \ + "with question_id #{question.id}, " \ + "author_id #{author_id} " \ + "and answer #{poll_answer.answer}" + tenant_info.to_s + logger.info(log_message) + duplicate_records_logger.info(log_message) + end + end end end end @@ -76,20 +85,13 @@ namespace :polls do end questions.each do |question| - options = question.question_options.joins(:translations).reorder(:id) - existing_choices = question.answers.where(option_id: nil).distinct.pluck(:answer) + option_finder = PollOptionFinder.new(question) - choices_map = existing_choices.to_h do |choice| - [choice, options.where("lower(title) = lower(?)", choice).distinct.ids] - end - - manageable_choices, unmanageable_choices = choices_map.partition { |choice, ids| ids.count == 1 } - - manageable_choices.each do |choice, ids| + option_finder.manageable_choices.each do |choice, ids| question.answers.where(option_id: nil, answer: choice).update_all(option_id: ids.first) end - unmanageable_choices.each do |choice, ids| + option_finder.unmanageable_choices.each do |choice, ids| tenant_info = " on tenant #{Tenant.current_schema}" unless Tenant.default? if ids.count == 0 diff --git a/spec/lib/tasks/polls_spec.rb b/spec/lib/tasks/polls_spec.rb index 22e6ca409..b3b2eb56b 100644 --- a/spec/lib/tasks/polls_spec.rb +++ b/spec/lib/tasks/polls_spec.rb @@ -94,6 +94,40 @@ describe "polls tasks" do expect(Poll::Answer.count).to eq 2 end + it "removes duplicate answers in different languages" do + question = create(:poll_question_multiple, max_votes: 2) + + create(:poll_question_option, question: question, title_en: "Yes", title_de: "Ja") + create(:poll_question_option, question: question, title_en: "No", title_de: "Nein") + create(:poll_question_option, question: question, title_en: "Maybe", title_de: "Vielleicht") + + create(:poll_answer, author: user, question: question, answer: "Yes", option: nil) + create(:poll_answer, author: user, question: question, answer: "Ja", option: nil) + + expect(Poll::Answer.count).to eq 2 + + Rake.application.invoke_task("polls:remove_duplicate_answers") + + expect(Poll::Answer.count).to eq 1 + end + + it "does not remove duplicate answers when many options are possible" do + question = create(:poll_question_multiple, title: "How do you pronounce it?", max_votes: 2) + + create(:poll_question_option, question: question, title_en: "A", title_es: "EI") + create(:poll_question_option, question: question, title_en: "E", title_es: "I") + create(:poll_question_option, question: question, title_en: "I", title_es: "AI") + + create(:poll_answer, question: question, author: user, answer: "I", option: nil) + create(:poll_answer, question: question, author: user, answer: "AI", option: nil) + + expect(Poll::Answer.count).to eq 2 + + Rake.application.invoke_task("polls:remove_duplicate_answers") + + expect(Poll::Answer.count).to eq 2 + end + it "removes duplicate answers on tenants" do create(:tenant, schema: "answers") @@ -176,6 +210,11 @@ describe "polls tasks" do user = create(:user, :level_two) question = create(:poll_question_multiple, :abc) + localized_question = create(:poll_question_multiple) + create(:poll_question_option, question: localized_question, title_en: "Yes", title_de: "Ja") + create(:poll_question_option, question: localized_question, title_en: "No", title_de: "Nein") + create(:poll_question_option, question: localized_question, title_en: "Maybe", title_de: "Vielleicht") + answer_attributes = { question_id: question.id, author_id: user.id, @@ -185,14 +224,23 @@ describe "polls tasks" do answer = create(:poll_answer, answer_attributes) insert(:poll_answer, answer_attributes) + localized_answer_attributes = { author: user, question: localized_question, option: nil } + localized_answer = create(:poll_answer, localized_answer_attributes.merge(answer: "Yes")) + create(:poll_answer, localized_answer_attributes.merge(answer: "Ja")) + answer.reload + localized_answer.reload + expect(answer.option_id).to be nil + expect(localized_answer.option_id).to be nil Rake.application.invoke_task("polls:populate_option_id") answer.reload + localized_answer.reload - expect(Poll::Answer.count).to eq 1 + expect(Poll::Answer.count).to eq 2 expect(answer.option_id).to eq question.question_options.find_by(title: "Answer A").id + expect(localized_answer.option_id).to eq localized_question.question_options.find_by(title: "Yes").id end it "populates the option_id column on tenants" do