diff --git a/app/models/concerns/statisticable.rb b/app/models/concerns/statisticable.rb index 0a041a851..cd24aed8e 100644 --- a/app/models/concerns/statisticable.rb +++ b/app/models/concerns/statisticable.rb @@ -12,32 +12,49 @@ module Statisticable self.class.stats_methods.map { |stat_name| [stat_name, send(stat_name)] }.to_h end + def total_male_participants + participants.where(gender: "male").count + end + + def total_female_participants + participants.where(gender: "female").count + end + + def total_unknown_gender_or_age + participants.where("gender IS NULL OR date_of_birth is NULL").uniq.count + end + + def male_percentage + calculate_percentage(total_male_participants, total_participants_with_gender) + end + + def female_percentage + calculate_percentage(total_female_participants, total_participants_with_gender) + end + + def participants_by_age + age_groups.map do |start, finish| + users = participants.where("date_of_birth > ? AND date_of_birth < ?", + finish.years.ago.beginning_of_year, + start.years.ago.end_of_year) + + [ + "#{start} - #{finish}", + { + range: range_description(start, finish), + count: users.count, + percentage: calculate_percentage(users.count, total_participants) + } + ] + end.to_h + end + private def total_participants_with_gender participants.where.not(gender: nil).distinct.count end - def total_male_participants - participants.where(gender: "male").count - end - - def total_female_participants - participants.where(gender: "female").count - end - - def total_unknown_gender_or_age - participants.where("gender IS NULL OR date_of_birth is NULL").uniq.count - end - - def male_percentage - calculate_percentage(total_male_participants, total_participants_with_gender) - end - - def female_percentage - calculate_percentage(total_female_participants, total_participants_with_gender) - end - def age_groups [[16, 19], [20, 24], @@ -58,23 +75,6 @@ module Statisticable ] end - def participants_by_age - age_groups.map do |start, finish| - users = participants.where("date_of_birth > ? AND date_of_birth < ?", - finish.years.ago.beginning_of_year, - start.years.ago.end_of_year) - - [ - "#{start} - #{finish}", - { - range: range_description(start, finish), - count: users.count, - percentage: calculate_percentage(users.count, total_participants) - } - ] - end.to_h - end - def calculate_percentage(fraction, total) return 0.0 if total.zero? diff --git a/app/models/poll/stats.rb b/app/models/poll/stats.rb index f8ad25560..21f9fba94 100644 --- a/app/models/poll/stats.rb +++ b/app/models/poll/stats.rb @@ -13,77 +13,78 @@ class Poll::Stats null_percentage_web null_percentage_booth total_null_percentage] end + def total_participants + total_participants_web + total_participants_booth + end + + %i[web booth].each do |channel| + define_method :"total_participants_#{channel}" do + send(:"total_#{channel}_valid") + + send(:"total_#{channel}_white") + + send(:"total_#{channel}_null") + end + + define_method :"total_participants_#{channel}_percentage" do + calculate_percentage(send(:"total_participants_#{channel}"), total_participants) + end + end + + def total_web_valid + voters.where(origin: "web").count - total_web_white + end + + def total_web_white + 0 + end + + def total_web_null + 0 + end + + def total_booth_valid + recounts.sum(:total_amount) + end + + def total_booth_white + recounts.sum(:white_amount) + end + + def total_booth_null + recounts.sum(:null_amount) + end + + def valid_percentage_web + calculate_percentage(total_web_valid, total_valid_votes) + end + + def white_percentage_web + calculate_percentage(total_web_white, total_white_votes) + end + + def null_percentage_web + calculate_percentage(total_web_null, total_null_votes) + end + + %i[valid white null].each do |type| + define_method :"#{type}_percentage_booth" do + calculate_percentage(send(:"total_booth_#{type}"), send(:"total_#{type}_votes")) + end + + define_method :"total_#{type}_votes" do + send(:"total_web_#{type}") + send(:"total_booth_#{type}") + end + + define_method :"total_#{type}_percentage" do + calculate_percentage(send(:"total_#{type}_votes"), total_participants) + end + end + private + def participants User.where(id: voters.pluck(:user_id)) end - def total_participants - total_participants_web + total_participants_booth - end - - %i[web booth].each do |channel| - define_method :"total_participants_#{channel}" do - send(:"total_#{channel}_valid") + - send(:"total_#{channel}_white") + - send(:"total_#{channel}_null") - end - - define_method :"total_participants_#{channel}_percentage" do - calculate_percentage(send(:"total_participants_#{channel}"), total_participants) - end - end - - def total_web_valid - voters.where(origin: "web").count - total_web_white - end - - def total_web_white - 0 - end - - def total_web_null - 0 - end - - def total_booth_valid - recounts.sum(:total_amount) - end - - def total_booth_white - recounts.sum(:white_amount) - end - - def total_booth_null - recounts.sum(:null_amount) - end - - def valid_percentage_web - calculate_percentage(total_web_valid, total_valid_votes) - end - - def white_percentage_web - calculate_percentage(total_web_white, total_white_votes) - end - - def null_percentage_web - calculate_percentage(total_web_null, total_null_votes) - end - - %i[valid white null].each do |type| - define_method :"#{type}_percentage_booth" do - calculate_percentage(send(:"total_booth_#{type}"), send(:"total_#{type}_votes")) - end - - define_method :"total_#{type}_votes" do - send(:"total_web_#{type}") + send(:"total_booth_#{type}") - end - - define_method :"total_#{type}_percentage" do - calculate_percentage(send(:"total_#{type}_votes"), total_participants) - end - end - def voters poll.voters end diff --git a/spec/models/poll/stats_spec.rb b/spec/models/poll/stats_spec.rb index b2130a370..5f6e345da 100644 --- a/spec/models/poll/stats_spec.rb +++ b/spec/models/poll/stats_spec.rb @@ -1,6 +1,160 @@ require "rails_helper" describe Poll::Stats do + let(:poll) { create(:poll) } + let(:stats) { Poll::Stats.new(poll) } + + describe "total participants" do + before { allow(stats).to receive(:total_web_white).and_return(1) } + + it "supports every channel" do + 3.times { create(:poll_voter, :from_web, poll: poll) } + create(:poll_recount, :from_booth, poll: poll, + total_amount: 8, white_amount: 4, null_amount: 1) + + expect(stats.total_participants_web).to eq(3) + expect(stats.total_participants_booth).to eq(13) + expect(stats.total_participants).to eq(16) + end + end + + describe "#total_participants_booth" do + it "uses recounts even if there are discrepancies when recounting" do + create(:poll_recount, :from_booth, poll: poll, total_amount: 1) + 2.times { create(:poll_voter, :from_booth, poll: poll) } + + expect(stats.total_participants_booth).to eq(1) + end + end + + describe "total participants percentage by channel" do + it "is relative to the total amount of participants" do + create(:poll_voter, :from_web, poll: poll) + create(:poll_recount, :from_booth, poll: poll, total_amount: 5) + + expect(stats.total_participants_web_percentage).to eq(16.667) + expect(stats.total_participants_booth_percentage).to eq(83.333) + end + end + + describe "#total_web_valid" do + before { allow(stats).to receive(:total_web_white).and_return(1) } + + it "returns only valid votes" do + 3.times { create(:poll_voter, :from_web, poll: poll) } + + expect(stats.total_web_valid).to eq(2) + end + end + + describe "#total_web_white" do + pending "Too complex to test" + end + + describe "#total_web_null" do + it "returns 0" do + expect(stats.total_web_null).to eq(0) + end + end + + describe "#total_booth_valid" do + it "sums the total amounts in the recounts" do + create(:poll_recount, :from_booth, poll: poll, total_amount: 3, white_amount: 1) + create(:poll_recount, :from_booth, poll: poll, total_amount: 4, null_amount: 2) + + expect(stats.total_booth_valid).to eq(7) + end + end + + describe "#total_booth_white" do + it "sums the white amounts in the recounts" do + create(:poll_recount, :from_booth, poll: poll, white_amount: 120, total_amount: 3) + create(:poll_recount, :from_booth, poll: poll, white_amount: 203, null_amount: 5) + + expect(stats.total_booth_white).to eq(323) + end + end + + describe "#total_booth_null" do + it "sums the null amounts in the recounts" do + create(:poll_recount, :from_booth, poll: poll, null_amount: 125, total_amount: 3) + create(:poll_recount, :from_booth, poll: poll, null_amount: 34, white_amount: 5) + + expect(stats.total_booth_null).to eq(159) + end + end + + describe "valid percentage by channel" do + it "is relative to the total amount of valid votes" do + create(:poll_recount, :from_booth, poll: poll, total_amount: 2) + create(:poll_voter, :from_web, poll: poll) + + expect(stats.valid_percentage_web).to eq(33.333) + expect(stats.valid_percentage_booth).to eq(66.667) + end + end + + describe "white percentage by channel" do + before { allow(stats).to receive(:total_web_white).and_return(10) } + + it "is relative to the total amount of white votes" do + create(:poll_recount, :from_booth, poll: poll, white_amount: 70) + + expect(stats.white_percentage_web).to eq(12.5) + expect(stats.white_percentage_booth).to eq(87.5) + end + end + + describe "null percentage by channel" do + it "only accepts null votes from booth" do + create(:poll_recount, :from_booth, poll: poll, null_amount: 70) + + expect(stats.null_percentage_web).to eq(0) + expect(stats.null_percentage_booth).to eq(100) + end + end + + describe "#total_valid_votes" do + it "counts valid votes from every channel" do + 2.times { create(:poll_voter, :from_web, poll: poll) } + create(:poll_recount, :from_booth, poll: poll, total_amount: 3, white_amount: 10) + create(:poll_recount, :from_booth, poll: poll, total_amount: 4, null_amount: 20) + + expect(stats.total_valid_votes).to eq(9) + end + end + + describe "#total_white_votes" do + before { allow(stats).to receive(:total_web_white).and_return(9) } + + it "counts white votes on every channel" do + create(:poll_recount, :from_booth, poll: poll, white_amount: 12) + + expect(stats.total_white_votes).to eq(21) + end + end + + describe "#total_null_votes" do + it "only accepts null votes from booth" do + create(:poll_recount, :from_booth, poll: poll, null_amount: 32) + + expect(stats.total_null_votes).to eq(32) + end + end + + describe "total percentage by type" do + before { allow(stats).to receive(:total_web_white).and_return(1) } + + it "is relative to the total amount of votes" do + 3.times { create(:poll_voter, :from_web, poll: poll) } + create(:poll_recount, :from_booth, poll: poll, + total_amount: 8, white_amount: 5, null_amount: 4) + + expect(stats.total_valid_percentage).to eq(50) + expect(stats.total_white_percentage).to eq(30) + expect(stats.total_null_percentage).to eq(20) + end + end describe "#generate" do it "generates the correct stats" do