Merge branch 'polls' into polls-question-show

This commit is contained in:
kikito
2017-01-26 11:36:34 +01:00
19 changed files with 239 additions and 59 deletions

View File

@@ -1,7 +1,7 @@
class Polls::QuestionsController < ApplicationController
load_and_authorize_resource :poll
load_and_authorize_resource :question, class: 'Poll::Question'#, through: :poll
load_and_authorize_resource :question, class: 'Poll::Question'
has_orders %w{most_voted newest oldest}, only: :show
@@ -10,17 +10,16 @@ class Polls::QuestionsController < ApplicationController
@comment_tree = CommentTree.new(@commentable, params[:page], @current_order)
set_comment_flags(@comment_tree.comments)
question_answer = @question.partial_results.where(author_id: current_user.try(:id)).first
question_answer = @question.answers.where(author_id: current_user.try(:id)).first
@answers_by_question_id = {@question.id => question_answer.try(:answer)}
end
def answer
partial_result = @question.partial_results.find_or_initialize_by(author: current_user,
amount: 1,
origin: 'web')
answer = @question.answers.find_or_initialize_by(author: current_user)
partial_result.answer = params[:answer]
partial_result.save!
answer.answer = params[:answer]
answer.save!
answer.record_voter_participation
@answers_by_question_id = {@question.id => params[:answer]}
end

View File

@@ -9,12 +9,12 @@ class PollsController < ApplicationController
end
def show
@questions = @poll.questions.for_render.sort_for_list
@questions = @poll.questions.for_render.sort_for_list
@answers_by_question_id = {}
poll_partial_results = Poll::PartialResult.by_question(@poll.question_ids).by_author(current_user.try(:id))
poll_partial_results.each do |result|
@answers_by_question_id[result.question_id] = result.answer
poll_answers = Poll::Answer.by_question(@poll.question_ids).by_author(current_user.try(:id))
poll_answers.each do |answer|
@answers_by_question_id[answer.question_id] = answer.answer
end
end

View File

@@ -1,7 +1,7 @@
class Poll < ActiveRecord::Base
has_many :booth_assignments, class_name: "Poll::BoothAssignment"
has_many :booths, through: :booth_assignments
has_many :voters, through: :booth_assignments
has_many :voters
has_many :officer_assignments, through: :booth_assignments
has_many :officers, through: :officer_assignments
has_many :questions

21
app/models/poll/answer.rb Normal file
View File

@@ -0,0 +1,21 @@
class Poll::Answer < ActiveRecord::Base
belongs_to :question, -> { with_hidden }
belongs_to :author, -> { with_hidden }, class_name: 'User', foreign_key: 'author_id'
has_one :voter
delegate :poll, :poll_id, to: :question
validates :question, presence: true
validates :author, presence: true
validates :answer, presence: true
validates :answer, inclusion: {in: ->(a) { a.question.valid_answers }}
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_from_user(author, {poll_id: poll_id, answer_id: id})
end
end

View File

@@ -1,31 +1,55 @@
class Poll
class Voter < ActiveRecord::Base
belongs_to :poll
belongs_to :booth_assignment
delegate :poll, to: :booth_assignment
belongs_to :answer
validates :booth_assignment, presence: true
validate :in_census
validate :has_not_voted
def in_census
errors.add(:document_number, :not_in_census) unless census_api_response.valid?
end
def has_not_voted
errors.add(:document_number, :has_voted, name: name) if has_voted?
end
validates :poll, presence: true
validates :document_number, presence: true, uniqueness: { scope: [:poll_id, :document_type], message: :has_voted }
def census_api_response
@census ||= CensusApi.new.call(document_type, document_number)
@census_api_response ||= CensusApi.new.call(document_type, document_number)
end
def has_voted?
poll.document_has_voted?(document_number, document_type)
def in_census?
census_api_response.valid?
end
def name
@census.name
def fill_stats_fields
if in_census?
self.gender = census_api_response.gender
self.geozone_id = Geozone.select(:id).where(census_code: census_api_response.district_code).first.try(:id)
self.age = voter_age(census_api_response.date_of_birth)
end
end
def self.create_from_user(user, options = {})
poll_id = options[:poll_id]
booth_assignment_id = options[:booth_assignment_id]
answer_id = options[:answer_id]
Voter.create(
document_type: user.document_type,
document_number: user.document_number,
poll_id: poll_id,
booth_assignment_id: booth_assignment_id,
gender: user.gender,
geozone_id: user.geozone_id,
age: user.age,
answer_id: answer_id
)
end
private
def voter_age(dob)
if dob.blank?
nil
else
now = Time.now.utc.to_date
now.year - dob.year - ((now.month > dob.month || (now.month == dob.month && now.day >= dob.day)) ? 0 : 1)
end
end
end
end

View File

@@ -246,6 +246,17 @@ class User < ActiveRecord::Base
"#{name} (#{email})"
end
def age
if date_of_birth.blank?
nil
else
now = Time.now.utc.to_date
now.year - date_of_birth.year - (
(now.month > date_of_birth.month || (now.month == date_of_birth.month && now.day >= date_of_birth.day)
) ? 0 : 1)
end
end
def save_requiring_finish_signup
begin
self.registering_with_oauth = true

View File

@@ -138,7 +138,7 @@ en:
attributes:
document_number:
not_in_census: "Document not in census"
has_voted: "%{name} has already voted"
has_voted: "User has already voted"
proposal:
attributes:
tag_list:

View File

@@ -133,7 +133,7 @@ es:
attributes:
document_number:
not_in_census: "Este documento no aparece en el censo"
has_voted: "%{name} ya ha votado"
has_voted: "Este usuario ya ha votado"
proposal:
attributes:
tag_list:

View File

@@ -0,0 +1,12 @@
class AddPollIdAndStatsFieldsToPollVoter < ActiveRecord::Migration
def change
add_column :poll_voters, :poll_id, :integer, null: false
remove_column :poll_voters, :booth_assignment_id, :integer, null: false
add_column :poll_voters, :booth_assignment_id, :integer
add_column :poll_voters, :age, :integer
add_column :poll_voters, :gender, :string
add_column :poll_voters, :geozone_id, :integer
end
end

View File

@@ -0,0 +1,15 @@
class CreatePollAnswers < ActiveRecord::Migration
def change
create_table :poll_answers do |t|
t.integer :question_id
t.integer :author_id
t.string :answer
t.timestamps
end
add_index :poll_answers, :question_id
add_index :poll_answers, :author_id
add_index :poll_answers, [:question_id, :answer]
end
end

View File

@@ -0,0 +1,9 @@
class AddAnswerIdToPollVoters < ActiveRecord::Migration
def change
add_column :poll_voters, :answer_id, :integer, default: nil
add_index :poll_voters, :document_number
add_index :poll_voters, :poll_id
add_index :poll_voters, [:poll_id, :document_number, :document_type], name: 'doc_by_poll'
end
end

View File

@@ -11,7 +11,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 20170120164547) do
ActiveRecord::Schema.define(version: 20170125114952) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@@ -374,6 +374,18 @@ ActiveRecord::Schema.define(version: 20170120164547) do
add_index "organizations", ["user_id"], name: "index_organizations_on_user_id", using: :btree
create_table "poll_answers", force: :cascade do |t|
t.integer "question_id"
t.integer "author_id"
t.string "answer"
t.datetime "created_at"
t.datetime "updated_at"
end
add_index "poll_answers", ["author_id"], name: "index_poll_answers_on_author_id", using: :btree
add_index "poll_answers", ["question_id", "answer"], name: "index_poll_answers_on_question_id_and_answer", using: :btree
add_index "poll_answers", ["question_id"], name: "index_poll_answers_on_question_id", using: :btree
create_table "poll_booth_assignments", force: :cascade do |t|
t.integer "booth_id"
t.integer "poll_id"
@@ -449,11 +461,20 @@ ActiveRecord::Schema.define(version: 20170120164547) do
create_table "poll_voters", force: :cascade do |t|
t.string "document_number"
t.string "document_type"
t.integer "booth_assignment_id", null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "poll_id", null: false
t.integer "booth_assignment_id"
t.integer "age"
t.string "gender"
t.integer "geozone_id"
t.integer "answer_id"
end
add_index "poll_voters", ["document_number"], name: "index_poll_voters_on_document_number", using: :btree
add_index "poll_voters", ["poll_id", "document_number", "document_type"], name: "doc_by_poll", using: :btree
add_index "poll_voters", ["poll_id"], name: "index_poll_voters_on_poll_id", using: :btree
create_table "polls", force: :cascade do |t|
t.string "name"
t.datetime "starts_at"

View File

@@ -421,7 +421,11 @@ FactoryGirl.define do
end
factory :poll_voter, class: 'Poll::Voter' do
association :booth_assignment, factory: :poll_booth_assignment
poll
trait :from_booth do
association :booth_assignment, factory: :poll_booth_assignment
end
trait :valid_document do
document_type "1"
@@ -434,11 +438,17 @@ FactoryGirl.define do
end
end
factory :poll_answer, class: 'Poll::Answer' do
association :question, factory: :poll_question
association :author, factory: [:user, :level_three]
answer { question.valid_answers.sample }
end
factory :poll_partial_result, class: 'Poll::PartialResult' do
association :question, factory: :poll_question
association :author, factory: :user
origin { 'web' }
answer { question.verified_answers.sample }
answer { question.valid_answers.sample }
end
factory :organization do

View File

@@ -166,7 +166,7 @@ feature 'Polls' do
scenario 'Level 2 users who have already answered' do
question = create(:poll_question, poll: poll, valid_answers: 'Han Solo, Chewbacca')
user = create(:user, :level_two)
create(:poll_partial_result, question: question, author: user, answer: 'Chewbacca')
create(:poll_answer, question: question, author: user, answer: 'Chewbacca')
login_as user
visit poll_path(poll)

View File

@@ -69,7 +69,7 @@ feature 'Poll Questions' do
question = create(:poll_question, poll: poll, valid_answers: 'Han Solo, Chewbacca')
user = create(:user, :level_two, geozone: geozone)
create(:poll_partial_result, question: question, author: user, answer: 'Chewbacca')
create(:poll_answer, question: question, author: user, answer: 'Chewbacca')
login_as user
visit question_path(question)
@@ -87,5 +87,21 @@ feature 'Poll Questions' do
expect(page).to have_link('Answer this question')
end
scenario 'Records participarion', :js do
question = create(:poll_question, poll: poll, valid_answers: 'Han Solo, Chewbacca')
user = create(:user, :level_two, geozone: geozone)
login_as user
visit question_path(question)
click_link 'Han Solo'
expect(page).to_not have_link('Han Solo')
answer = Poll::Answer.by_question(question.id).by_author(user.id).first
expect(answer.voter.document_number).to eq(user.document_number)
expect(answer.voter.poll_id).to eq(poll.id)
end
end
end

View File

@@ -0,0 +1,30 @@
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
end
end
describe "#record_voter_participation" do
it "creates a poll_voter with user and poll data" do
answer = create(:poll_answer)
expect(answer.voter).to be_nil
answer.record_voter_participation
voter = answer.reload.voter
expect(voter.answer).to eq(answer)
expect(voter.document_number).to eq(answer.author.document_number)
expect(voter.poll_id).to eq(answer.poll.id)
end
end
end

View File

@@ -64,15 +64,14 @@ describe :poll do
describe "#document_has_voted?" do
it "returns true if Poll::Voter with document exists" do
booth_assignment = create(:poll_booth_assignment, poll: poll)
voter = create(:poll_voter, :valid_document, booth_assignment: booth_assignment)
voter = create(:poll_voter, :valid_document, poll: poll)
expect(poll.document_has_voted?(voter.document_number, voter.document_type)).to eq(true)
end
it "returns false if Poll::Voter with document does not exists" do
booth_assignment = create(:poll_booth_assignment)
voter = create(:poll_voter, :valid_document, booth_assignment: booth_assignment)
poll_2 = create(:poll)
voter = create(:poll_voter, :valid_document, poll: poll_2)
expect(poll.document_has_voted?(voter.document_number, voter.document_type)).to eq(false)
end

View File

@@ -8,36 +8,37 @@ describe :voter do
describe "validations" do
it "should be valid if in census and has not voted" do
voter = build(:poll_voter, :valid_document, booth_assignment: booth_assignment)
it "should be valid if has not voted" do
voter = build(:poll_voter, :valid_document)
expect(voter).to be_valid
end
it "should not be valid if the user is not in the census" do
voter = build(:poll_voter, :invalid_document, booth_assignment: booth_assignment)
expect(voter).to_not be_valid
expect(voter.errors.messages[:document_number]).to eq(["Document not in census"])
end
it "should not be valid if the user has already voted in the same booth/poll" do
voter1 = create(:poll_voter, :valid_document, booth_assignment: booth_assignment)
voter2 = build(:poll_voter, :valid_document, booth_assignment: booth_assignment)
it "should not be valid if the user has already voted in the same poll or booth_assignment" do
voter1 = create(:poll_voter, :valid_document, poll: poll)
voter2 = build(:poll_voter, :valid_document, poll: poll)
expect(voter2).to_not be_valid
expect(voter2.errors.messages[:document_number]).to eq(["José García has already voted"])
expect(voter2.errors.messages[:document_number]).to eq(["User has already voted"])
end
it "should not be valid if the user has already voted in the same poll/booth" do
voter1 = create(:poll_voter, :valid_document, poll: poll, booth_assignment: booth_assignment)
voter2 = build(:poll_voter, :valid_document, poll: poll, booth_assignment: booth_assignment)
expect(voter2).to_not be_valid
expect(voter2.errors.messages[:document_number]).to eq(["User has already voted"])
end
it "should not be valid if the user has already voted in different booth in the same poll" do
booth_assignment1 = create(:poll_booth_assignment, poll: poll)
booth_assignment2 = create(:poll_booth_assignment, poll: poll)
voter1 = create(:poll_voter, :valid_document, booth_assignment: booth_assignment1)
voter2 = build(:poll_voter, :valid_document, booth_assignment: booth_assignment2)
voter1 = create(:poll_voter, :valid_document, poll: poll, booth_assignment: booth_assignment1)
voter2 = build(:poll_voter, :valid_document, poll: poll, booth_assignment: booth_assignment2)
expect(voter2).to_not be_valid
expect(voter2.errors.messages[:document_number]).to eq(["José García has already voted"])
expect(voter2.errors.messages[:document_number]).to eq(["User has already voted"])
end
it "should be valid if the user has already voted in the same booth in different poll" do
@@ -50,8 +51,13 @@ describe :voter do
expect(voter2).to be_valid
end
xit "should not be valid if the user has voted via web" do
pending "Implementation for voting via web"
it "should not be valid if the user has voted via web" do
answer = create(:poll_answer)
answer.record_voter_participation
voter = build(:poll_voter, poll: answer.question.poll, document_number: answer.author.document_number, document_type: "1")
expect(voter).to_not be_valid
expect(voter.errors.messages[:document_number]).to eq(["User has already voted"])
end
end

View File

@@ -65,6 +65,13 @@ describe User do
end
end
describe "#age" do
it "is the rounded integer age based on the date_of_birth" do
user = create(:user, date_of_birth: 33.years.ago)
expect(user.age).to eq(33)
end
end
describe 'preferences' do
describe 'email_on_comment' do
it 'should be false by default' do