Merge pull request #1965 from consul/polls-voting

Polls voting
This commit is contained in:
BertoCQ
2017-10-02 22:25:51 +02:00
committed by GitHub
16 changed files with 326 additions and 14 deletions

View File

@@ -12,7 +12,8 @@ class Officing::VotersController < Officing::BaseController
@voter = Poll::Voter.new(document_type: @user.document_type,
document_number: @user.document_number,
user: @user,
poll: @poll)
poll: @poll,
origin: "booth")
@voter.save!
end

View File

@@ -59,6 +59,10 @@ class Poll < ActiveRecord::Base
voters.where(document_number: document_number, document_type: document_type).exists?
end
def voted_in_booth?(user)
Poll::Voter.where(poll: self, user: user, origin: "booth").exists?
end
def date_range
unless starts_at.present? && ends_at.present? && starts_at <= ends_at
errors.add(:starts_at, I18n.t('errors.messages.invalid_date_range'))

View File

@@ -8,12 +8,13 @@ class Poll::Answer < ActiveRecord::Base
validates :question, presence: true
validates :author, presence: true
validates :answer, presence: true
validates :answer, inclusion: {in: ->(a) { a.question.valid_answers }}
validates :answer, inclusion: { in: ->(a) { a.question.valid_answers }},
unless: ->(a) { a.question.blank? }
scope :by_author, ->(author_id) { where(author_id: author_id) }
scope :by_question, ->(question_id) { where(question_id: question_id) }
def record_voter_participation
Poll::Voter.create!(user: author, poll: poll)
Poll::Voter.find_or_create_by!(user: author, poll: poll, origin: "web")
end
end

View File

@@ -1,5 +1,8 @@
class Poll
class Voter < ActiveRecord::Base
VALID_ORIGINS = %w{ web booth }
belongs_to :poll
belongs_to :user
belongs_to :geozone
@@ -10,9 +13,13 @@ class Poll
validates :user_id, presence: true
validates :document_number, presence: true, uniqueness: { scope: [:poll_id, :document_type], message: :has_voted }
validates :origin, inclusion: { in: VALID_ORIGINS }
before_validation :set_demographic_info, :set_document_info
scope :web, -> { where(origin: 'web') }
scope :booth, -> { where(origin: 'booth') }
def set_demographic_info
return if user.blank?

View File

@@ -31,8 +31,14 @@
<div class="row margin-top">
<div class="small-12 medium-9 column">
<% @questions.each do |question| %>
<%= render 'polls/questions/question', question: question %>
<% if @poll.voted_in_booth?(current_user) %>
<div class="callout warning">
<%= t("polls.show.already_voted_in_booth") %>
</div>
<% else %>
<% @questions.each do |question| %>
<%= render 'polls/questions/question', question: question %>
<% end %>
<% end %>
</div>

View File

@@ -481,6 +481,7 @@ en:
help_text_1: "Voting takes place when a citizen proposal supports reaches 1% of the census with voting rights. Voting can also include questions that the City Council ask to the citizens decision."
help_text_2: "To participate in the next vote you have to sign up on %{org} and verify your account. All registered voters in the city over 16 years old can vote. The results of all votes are binding on the government."
show:
already_voted_in_booth: "You have already participated in a booth for this poll."
dates_title: "Participation dates"
cant_answer_not_logged_in: "You must %{signin} or %{signup} to participate."
signin: Sign in

View File

@@ -481,6 +481,7 @@ es:
help_text_1: "Las votaciones se convocan cuando una propuesta ciudadana alcanza el 1% de apoyos del censo con derecho a voto. En las votaciones también se pueden incluir cuestiones que el Ayuntamiento somete a decisión directa de la ciudadanía."
help_text_2: "Para participar en la próxima votación tienes que registrarte en %{org} y verificar tu cuenta. Pueden votar todas las personas empadronadas en la ciudad mayores de 16 años. Los resultados de todas las votaciones serán vinculantes para el gobierno."
show:
already_voted_in_booth: "Ya has participado en esta votación en una urna."
dates_title: "Fechas de participación"
cant_answer_not_logged_in: "Necesitas %{signin} o %{signup} para participar."
signin: iniciar sesión

View File

@@ -0,0 +1,5 @@
class AddOriginToPollVoters < ActiveRecord::Migration
def change
add_column :poll_voters, :origin, :string
end
end

View File

@@ -752,6 +752,7 @@ ActiveRecord::Schema.define(version: 20171002191347) do
t.integer "answer_id"
t.integer "officer_assignment_id"
t.integer "user_id"
t.string "origin"
end
add_index "poll_voters", ["booth_assignment_id"], name: "index_poll_voters_on_booth_assignment_id", using: :btree

View File

@@ -52,6 +52,12 @@ FactoryGirl.define do
trait :verified do
verified_at Time.current
end
trait :in_census do
document_number "12345678Z"
document_type "1"
verified_at Time.current
end
end
factory :identity do
@@ -525,6 +531,7 @@ FactoryGirl.define do
factory :poll_voter, class: 'Poll::Voter' do
poll
association :user, :level_two
origin "web"
trait :from_booth do
association :booth_assignment, factory: :poll_booth_assignment

View File

@@ -184,6 +184,7 @@ feature 'Polls' do
poll.geozones << geozone
create(:poll_question, poll: poll, valid_answers: 'Han Solo, Chewbacca')
user = create(:user, :level_two, geozone: geozone)
login_as user
visit poll_path(poll)
@@ -193,5 +194,25 @@ feature 'Polls' do
expect(page).to have_link('Chewbacca')
end
scenario 'Level 2 users changing answer', :js do
poll.update(geozone_restricted: true)
poll.geozones << geozone
create(:poll_question, poll: poll, valid_answers: 'Han Solo, Chewbacca')
user = create(:user, :level_two, geozone: geozone)
login_as user
visit poll_path(poll)
click_link 'Han Solo'
expect(page).to_not have_link('Han Solo')
expect(page).to have_link('Chewbacca')
click_link 'Chewbacca'
expect(page).to_not have_link('Chewbacca')
expect(page).to have_link('Han Solo')
end
end
end

View File

@@ -0,0 +1,94 @@
require 'rails_helper'
feature "Voter" do
context "Origin" do
scenario "Voting via web", :js do
poll = create(:poll)
question = create(:poll_question, poll: poll, valid_answers: 'Yes, No')
user = create(:user, :level_two)
login_as user
visit question_path(question)
click_link 'Answer this question'
click_link 'Yes'
expect(page).to_not have_link('Yes')
expect(Poll::Voter.count).to eq(1)
expect(Poll::Voter.first.origin).to eq("web")
end
scenario "Voting in booth", :js do
user = create(:user, :in_census)
create(:geozone, :in_census)
poll = create(:poll)
officer = create(:poll_officer)
ba = create(:poll_booth_assignment, poll: poll)
create(:poll_officer_assignment, officer: officer, booth_assignment: ba)
login_through_form_as_officer(officer.user)
visit new_officing_residence_path
officing_verify_residence
expect(page).to have_content poll.name
first(:button, "Confirm vote").click
expect(page).to have_content "Vote introduced!"
expect(Poll::Voter.count).to eq(1)
expect(Poll::Voter.first.origin).to eq("booth")
end
context "Trying to vote the same poll in booth and web" do
let(:poll) { create(:poll) }
let(:question) { create(:poll_question, poll: poll, valid_answers: 'Yes, No') }
let!(:user) { create(:user, :in_census) }
let(:officer) { create(:poll_officer) }
let(:ba) { create(:poll_booth_assignment, poll: poll) }
let!(:oa) { create(:poll_officer_assignment, officer: officer, booth_assignment: ba) }
scenario "Trying to vote in web and then in booth", :js do
login_as user
vote_for_poll_via_web
click_link "Sign out"
login_through_form_as_officer(officer.user)
visit new_officing_residence_path
officing_verify_residence
expect(page).to have_content poll.name
expect(page).to_not have_button "Confirm vote"
expect(page).to have_content "Has already participated in this poll"
end
scenario "Trying to vote in booth and then in web", :js do
login_through_form_as_officer(officer.user)
vote_for_poll_via_booth
visit root_path
click_link "Sign out"
login_as user
visit question_path(question)
click_link 'Answer this question'
expect(page).to_not have_link('Yes')
expect(page).to have_content "You have already participated in a booth for this poll."
expect(Poll::Voter.count).to eq(1)
end
end
end
end

View File

@@ -3,28 +3,71 @@ require 'rails_helper'
describe Poll::Answer do
describe "validations" do
it "validates that the answers are included in the Poll::Question's list" do
q = create(:poll_question, valid_answers: 'One, Two, Three')
expect(build(:poll_answer, question: q, answer: 'One')).to be_valid
expect(build(:poll_answer, question: q, answer: 'Two')).to be_valid
expect(build(:poll_answer, question: q, answer: 'Three')).to be_valid
expect(build(:poll_answer, question: q, answer: 'Four')).to_not be_valid
let(:answer) { build(:poll_answer) }
it "should be valid" do
expect(answer).to be_valid
end
it "should not be valid wihout a question" do
answer.question = nil
expect(answer).to_not be_valid
end
it "should not be valid without an author" do
answer.author = nil
expect(answer).to_not be_valid
end
it "should not be valid without an answer" do
answer.answer = nil
expect(answer).to_not be_valid
end
it "should be valid for answers included in the Poll::Question's list" do
question = create(:poll_question, valid_answers: 'One, Two, Three')
expect(build(:poll_answer, question: question, answer: 'One')).to be_valid
expect(build(:poll_answer, question: question, answer: 'Two')).to be_valid
expect(build(:poll_answer, question: question, answer: 'Three')).to be_valid
expect(build(:poll_answer, question: question, answer: 'Four')).to_not be_valid
end
end
describe "#record_voter_participation" do
let(:author) { create(:user, :level_two) }
let(:poll) { create(:poll) }
let(:question) { create(:poll_question, poll: poll, valid_answers: "Yes, No") }
it "creates a poll_voter with user and poll data" do
answer = create(:poll_answer)
answer = create(:poll_answer, question: question, author: author, answer: "Yes")
expect(answer.poll.voters).to be_blank
answer.record_voter_participation
expect(answer.poll.reload.voters.size).to eq(1)
voter = answer.poll.voters.first
expect(poll.reload.voters.size).to eq(1)
voter = poll.voters.first
expect(voter.document_number).to eq(answer.author.document_number)
expect(voter.poll_id).to eq(answer.poll.id)
end
it "updates a poll_voter with user and poll data" do
answer = create(:poll_answer, question: question, author: author, answer: "Yes")
answer.record_voter_participation
expect(poll.reload.voters.size).to eq(1)
answer = create(:poll_answer, question: question, author: author, answer: "No")
answer.record_voter_participation
expect(poll.reload.voters.size).to eq(1)
voter = poll.voters.first
expect(voter.document_number).to eq(answer.author.document_number)
expect(voter.poll_id).to eq(answer.poll.id)
end
end
end

View File

@@ -138,4 +138,33 @@ describe :poll do
end
end
end
describe "#voted_in_booth?" do
it "returns true if the user has already voted in booth" do
user = create(:user, :level_two)
poll = create(:poll)
create(:poll_voter, poll: poll, user: user, origin: "booth")
expect(poll.voted_in_booth?(user)).to be
end
it "returns false if the user has not already voted in a booth" do
user = create(:user, :level_two)
poll = create(:poll)
expect(poll.voted_in_booth?(user)).to_not be
end
it "returns false if the user has voted in web" do
user = create(:user, :level_two)
poll = create(:poll)
create(:poll_voter, poll: poll, user: user, origin: "web")
expect(poll.voted_in_booth?(user)).to_not be
end
end
end

View File

@@ -83,6 +83,64 @@ describe :voter do
expect(voter.errors.messages[:document_number]).to eq(["User has already voted"])
end
context "origin" do
it "should not be valid without an origin" do
voter.origin = nil
expect(voter).to_not be_valid
end
it "should not be valid without a valid origin" do
voter.origin = "invalid_origin"
expect(voter).to_not be_valid
end
it "should be valid with a booth origin" do
voter.origin = "booth"
expect(voter).to be_valid
end
it "should be valid with a web origin" do
voter.origin = "web"
expect(voter).to be_valid
end
end
end
describe "scopes" do
describe "#web" do
it "returns voters with a web origin" do
voter1 = create(:poll_voter, origin: "web")
voter2 = create(:poll_voter, origin: "web")
voter3 = create(:poll_voter, origin: "booth")
web_voters = Poll::Voter.web
expect(web_voters.count).to eq(2)
expect(web_voters).to include(voter1)
expect(web_voters).to include(voter2)
expect(web_voters).to_not include(voter3)
end
end
describe "#booth" do
it "returns voters with a booth origin" do
voter1 = create(:poll_voter, origin: "booth")
voter2 = create(:poll_voter, origin: "booth")
voter3 = create(:poll_voter, origin: "web")
booth_voters = Poll::Voter.booth
expect(booth_voters.count).to eq(2)
expect(booth_voters).to include(voter1)
expect(booth_voters).to include(voter2)
expect(booth_voters).to_not include(voter3)
end
end
end
describe "save" do

View File

@@ -24,6 +24,17 @@ module CommonActions
click_button 'Enter'
end
def login_through_form_as_officer(user)
visit root_path
click_link 'Sign in'
fill_in 'user_login', with: user.email
fill_in 'user_password', with: user.password
click_button 'Enter'
visit new_officing_residence_path
end
def login_as_authenticated_manager
expected_response = {login: login, user_key: user_key, date: date}.with_indifferent_access
login, user_key, date = "JJB042", "31415926", Time.current.strftime("%Y%m%d%H%M%S")
@@ -287,4 +298,26 @@ module CommonActions
end
end
def vote_for_poll_via_web
visit question_path(question)
click_link 'Answer this question'
click_link 'Yes'
expect(page).to_not have_link('Yes')
expect(Poll::Voter.count).to eq(1)
end
def vote_for_poll_via_booth
visit new_officing_residence_path
officing_verify_residence
expect(page).to have_content poll.name
first(:button, "Confirm vote").click
expect(page).to have_content "Vote introduced!"
expect(Poll::Voter.count).to eq(1)
end
end