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 class Polls::QuestionsController < ApplicationController
load_and_authorize_resource :poll 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 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) @comment_tree = CommentTree.new(@commentable, params[:page], @current_order)
set_comment_flags(@comment_tree.comments) 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)} @answers_by_question_id = {@question.id => question_answer.try(:answer)}
end end
def answer def answer
partial_result = @question.partial_results.find_or_initialize_by(author: current_user, answer = @question.answers.find_or_initialize_by(author: current_user)
amount: 1,
origin: 'web')
partial_result.answer = params[:answer] answer.answer = params[:answer]
partial_result.save! answer.save!
answer.record_voter_participation
@answers_by_question_id = {@question.id => params[:answer]} @answers_by_question_id = {@question.id => params[:answer]}
end end

View File

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

View File

@@ -1,7 +1,7 @@
class Poll < ActiveRecord::Base class Poll < ActiveRecord::Base
has_many :booth_assignments, class_name: "Poll::BoothAssignment" has_many :booth_assignments, class_name: "Poll::BoothAssignment"
has_many :booths, through: :booth_assignments has_many :booths, through: :booth_assignments
has_many :voters, through: :booth_assignments has_many :voters
has_many :officer_assignments, through: :booth_assignments has_many :officer_assignments, through: :booth_assignments
has_many :officers, through: :officer_assignments has_many :officers, through: :officer_assignments
has_many :questions 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 Poll
class Voter < ActiveRecord::Base class Voter < ActiveRecord::Base
belongs_to :poll
belongs_to :booth_assignment belongs_to :booth_assignment
delegate :poll, to: :booth_assignment belongs_to :answer
validates :booth_assignment, presence: true validates :poll, presence: true
validate :in_census validates :document_number, presence: true, uniqueness: { scope: [:poll_id, :document_type], message: :has_voted }
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
def census_api_response def census_api_response
@census ||= CensusApi.new.call(document_type, document_number) @census_api_response ||= CensusApi.new.call(document_type, document_number)
end end
def has_voted? def in_census?
poll.document_has_voted?(document_number, document_type) census_api_response.valid?
end end
def name def fill_stats_fields
@census.name 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 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
end end

View File

@@ -246,6 +246,17 @@ class User < ActiveRecord::Base
"#{name} (#{email})" "#{name} (#{email})"
end 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 def save_requiring_finish_signup
begin begin
self.registering_with_oauth = true self.registering_with_oauth = true

View File

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

View File

@@ -133,7 +133,7 @@ es:
attributes: attributes:
document_number: document_number:
not_in_census: "Este documento no aparece en el censo" not_in_census: "Este documento no aparece en el censo"
has_voted: "%{name} ya ha votado" has_voted: "Este usuario ya ha votado"
proposal: proposal:
attributes: attributes:
tag_list: 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. # 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 # These are extensions that must be enabled in order to support this database
enable_extension "plpgsql" 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 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| create_table "poll_booth_assignments", force: :cascade do |t|
t.integer "booth_id" t.integer "booth_id"
t.integer "poll_id" t.integer "poll_id"
@@ -449,11 +461,20 @@ ActiveRecord::Schema.define(version: 20170120164547) do
create_table "poll_voters", force: :cascade do |t| create_table "poll_voters", force: :cascade do |t|
t.string "document_number" t.string "document_number"
t.string "document_type" t.string "document_type"
t.integer "booth_assignment_id", null: false
t.datetime "created_at", null: false t.datetime "created_at", null: false
t.datetime "updated_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 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| create_table "polls", force: :cascade do |t|
t.string "name" t.string "name"
t.datetime "starts_at" t.datetime "starts_at"

View File

@@ -421,7 +421,11 @@ FactoryGirl.define do
end end
factory :poll_voter, class: 'Poll::Voter' do 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 trait :valid_document do
document_type "1" document_type "1"
@@ -434,11 +438,17 @@ FactoryGirl.define do
end end
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 factory :poll_partial_result, class: 'Poll::PartialResult' do
association :question, factory: :poll_question association :question, factory: :poll_question
association :author, factory: :user association :author, factory: :user
origin { 'web' } origin { 'web' }
answer { question.verified_answers.sample } answer { question.valid_answers.sample }
end end
factory :organization do factory :organization do

View File

@@ -166,7 +166,7 @@ feature 'Polls' do
scenario 'Level 2 users who have already answered' do scenario 'Level 2 users who have already answered' do
question = create(:poll_question, poll: poll, valid_answers: 'Han Solo, Chewbacca') question = create(:poll_question, poll: poll, valid_answers: 'Han Solo, Chewbacca')
user = create(:user, :level_two) 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 login_as user
visit poll_path(poll) 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') question = create(:poll_question, poll: poll, valid_answers: 'Han Solo, Chewbacca')
user = create(:user, :level_two, geozone: geozone) 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 login_as user
visit question_path(question) visit question_path(question)
@@ -87,5 +87,21 @@ feature 'Poll Questions' do
expect(page).to have_link('Answer this question') expect(page).to have_link('Answer this question')
end 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
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 describe "#document_has_voted?" do
it "returns true if Poll::Voter with document exists" 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, poll: poll)
voter = create(:poll_voter, :valid_document, booth_assignment: booth_assignment)
expect(poll.document_has_voted?(voter.document_number, voter.document_type)).to eq(true) expect(poll.document_has_voted?(voter.document_number, voter.document_type)).to eq(true)
end end
it "returns false if Poll::Voter with document does not exists" do it "returns false if Poll::Voter with document does not exists" do
booth_assignment = create(:poll_booth_assignment) poll_2 = create(:poll)
voter = create(:poll_voter, :valid_document, booth_assignment: booth_assignment) voter = create(:poll_voter, :valid_document, poll: poll_2)
expect(poll.document_has_voted?(voter.document_number, voter.document_type)).to eq(false) expect(poll.document_has_voted?(voter.document_number, voter.document_type)).to eq(false)
end end

View File

@@ -8,36 +8,37 @@ describe :voter do
describe "validations" do describe "validations" do
it "should be valid if in census and has not voted" do it "should be valid if has not voted" do
voter = build(:poll_voter, :valid_document, booth_assignment: booth_assignment) voter = build(:poll_voter, :valid_document)
expect(voter).to be_valid expect(voter).to be_valid
end end
it "should not be valid if the user is not in the census" do it "should not be valid if the user has already voted in the same poll or booth_assignment" do
voter = build(:poll_voter, :invalid_document, booth_assignment: booth_assignment) voter1 = create(:poll_voter, :valid_document, poll: poll)
voter2 = build(:poll_voter, :valid_document, poll: poll)
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)
expect(voter2).to_not be_valid 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 end
it "should not be valid if the user has already voted in different booth in the same poll" do 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_assignment1 = create(:poll_booth_assignment, poll: poll)
booth_assignment2 = create(:poll_booth_assignment, poll: poll) booth_assignment2 = create(:poll_booth_assignment, poll: poll)
voter1 = create(:poll_voter, :valid_document, booth_assignment: booth_assignment1) voter1 = create(:poll_voter, :valid_document, poll: poll, booth_assignment: booth_assignment1)
voter2 = build(:poll_voter, :valid_document, booth_assignment: booth_assignment2) voter2 = build(:poll_voter, :valid_document, poll: poll, booth_assignment: booth_assignment2)
expect(voter2).to_not be_valid 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 end
it "should be valid if the user has already voted in the same booth in different poll" do 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 expect(voter2).to be_valid
end end
xit "should not be valid if the user has voted via web" do it "should not be valid if the user has voted via web" do
pending "Implementation for voting via web" 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
end end

View File

@@ -65,6 +65,13 @@ describe User do
end end
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 'preferences' do
describe 'email_on_comment' do describe 'email_on_comment' do
it 'should be false by default' do it 'should be false by default' do