diff --git a/lib/tasks/consul.rake b/lib/tasks/consul.rake index 0e89a360f..64225f60b 100644 --- a/lib/tasks/consul.rake +++ b/lib/tasks/consul.rake @@ -9,6 +9,6 @@ namespace :consul do task "execute_release_2.2.0_tasks": [ "db:mask_ips", "polls:remove_duplicate_voters", - "polls:remove_duplicate_answers" + "polls:populate_option_id" ] end diff --git a/lib/tasks/polls.rake b/lib/tasks/polls.rake index bda3058eb..7b9472145 100644 --- a/lib/tasks/polls.rake +++ b/lib/tasks/polls.rake @@ -61,4 +61,48 @@ namespace :polls do end end end + + desc "populates the poll answers option_id column" + task populate_option_id: :remove_duplicate_answers do + logger = ApplicationLogger.new + logger.info "Updating option_id column in poll_answers" + + Tenant.run_on_each do + questions = Poll::Question.select do |question| + # Change this if you've added a custom votation type + # to your Consul Democracy installation that implies + # choosing among a few given options + question.unique? || question.multiple? + end + + questions.each do |question| + options = question.question_options.joins(:translations).reorder(:id) + existing_choices = question.answers.where(option_id: nil).distinct.pluck(:answer) + + 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| + question.answers.where(option_id: nil, answer: choice).update_all(option_id: ids.first) + end + + unmanageable_choices.each do |choice, ids| + tenant_info = " on tenant #{Tenant.current_schema}" unless Tenant.default? + + if ids.count == 0 + logger.warn "Skipping poll_answers with the text \"#{choice}\" for the poll_question " \ + "with ID #{question.id}. This question has no poll_question_answers " \ + "containing the text \"#{choice}\"" + tenant_info.to_s + else + logger.warn "Skipping poll_answers with the text \"#{choice}\" for the poll_question " \ + "with ID #{question.id}. The text \"#{choice}\" could refer to any of these " \ + "IDs in the poll_question_answers table: #{ids.join(", ")}" + tenant_info.to_s + end + end + end + end + end end diff --git a/spec/lib/tasks/polls_spec.rb b/spec/lib/tasks/polls_spec.rb index da5050564..22e6ca409 100644 --- a/spec/lib/tasks/polls_spec.rb +++ b/spec/lib/tasks/polls_spec.rb @@ -120,4 +120,101 @@ describe "polls tasks" do end end end + + describe "polls:populate_option_id" do + before do + Rake::Task["polls:remove_duplicate_answers"].reenable + Rake::Task["polls:populate_option_id"].reenable + end + + it "populates the option_id column of existing answers when there's one valid answer" do + yes_no_question = create(:poll_question, :yes_no, poll: poll) + abc_question = create(:poll_question_multiple, :abc, poll: poll) + option_a = abc_question.question_options.find_by(title: "Answer A") + option_b = abc_question.question_options.find_by(title: "Answer B") + + answer = create(:poll_answer, question: yes_no_question, author: user, answer: "Yes", option: nil) + abc_answer = create(:poll_answer, question: abc_question, author: user, answer: "Answer A", option: nil) + answer_with_inconsistent_option = create(:poll_answer, question: abc_question, + author: user, + answer: "Answer A", + option: option_b) + answer_with_invalid_option = build(:poll_answer, question: abc_question, + author: user, + answer: "Non existing", + option: nil) + answer_with_invalid_option.save!(validate: false) + + Rake.application.invoke_task("polls:populate_option_id") + answer.reload + abc_answer.reload + answer_with_inconsistent_option.reload + answer_with_invalid_option.reload + + expect(answer.option_id).to eq yes_no_question.question_options.find_by(title: "Yes").id + expect(abc_answer.option_id).to eq option_a.id + expect(answer_with_inconsistent_option.option_id).to eq option_b.id + expect(answer_with_invalid_option.option_id).to be nil + end + + it "does not populate the option_id column when there are several valid options" do + question = create(:poll_question, title: "How do you pronounce it?") + + 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") + + answer = create(:poll_answer, question: question, author: user, answer: "I", option: nil) + + Rake.application.invoke_task("polls:populate_option_id") + answer.reload + + expect(answer.option_id).to be nil + end + + it "removes duplicate answers before populating the option_id column" do + user = create(:user, :level_two) + question = create(:poll_question_multiple, :abc) + + answer_attributes = { + question_id: question.id, + author_id: user.id, + answer: "Answer A", + option_id: nil + } + answer = create(:poll_answer, answer_attributes) + insert(:poll_answer, answer_attributes) + + answer.reload + expect(answer.option_id).to be nil + + Rake.application.invoke_task("polls:populate_option_id") + answer.reload + + expect(Poll::Answer.count).to eq 1 + expect(answer.option_id).to eq question.question_options.find_by(title: "Answer A").id + end + + it "populates the option_id column on tenants" do + create(:tenant, schema: "answers") + + Tenant.switch("answers") do + question = create(:poll_question_multiple, :abc) + + create(:poll_answer, question: question, answer: "Answer A", option: nil) + end + + Rake.application.invoke_task("polls:populate_option_id") + + Tenant.switch("answers") do + expect(Poll::Question.count).to eq 1 + expect(Poll::Answer.count).to eq 1 + + question = Poll::Question.first + option_a = question.question_options.find_by(title: "Answer A") + + expect(Poll::Answer.first.option_id).to eq option_a.id + end + end + end end