holderdeord/hdo-site

View on GitHub
lib/hdo/stats/vote_counts.rb

Summary

Maintainability
A
2 hrs
Test Coverage
module Hdo
  module Stats
    class VoteCounts
      attr_reader :for_count, :against_count, :absent_count

      def initialize(vote)
        @for_count     = vote.for_count     || 0
        @against_count = vote.against_count || 0
        @absent_count  = vote.absent_count  || 0

        @party_counts = compute_party_counts vote
      end

      def as_json(opts = nil)
        {
          approve: for_count,
          against: against_count,
          absent:  absent_count,
          parties: Hash[@party_counts.map { |party, counts| [party && party.name, counts] }],
          groups: groups.inject({}) { |a, (k,v)| a.merge(k => v.compact.map { |p| {name: p.name, slug: p.slug } }) }
        }
      end

      def vote_count
        for_count + against_count
      end

      def total_count
        vote_count + absent_count
      end

      def for_percent
        percentage_of for_count, vote_count
      end

      def against_percent
        percentage_of against_count, vote_count
      end

      def absent_percent
        percentage_of absent_count, total_count
      end

      def percentage_of(count, total)
        (count * 100 / (total.zero? ? 1 : total).to_f).round
      end

      def party_counts_for(party)
        @party_counts[party] || Hash.new(0)
      end

      def party_participated?(party)
        counts = party_counts_for(party)
        counts[:for] > 0 || counts[:against] > 0
      end

      def party_for?(party)
        counts = party_counts_for(party)
        counts[:for] > counts[:against]
      end

      def party_against?(party)
        counts = party_counts_for(party)
        counts[:against] > counts[:for]
      end

      def party_absent?(party)
        counts = party_counts_for(party)
        counts[:absent] > 0 && (counts[:for] == 0 && counts[:against] == 0)
      end

      def party_split?(party)
        counts = party_counts_for(party)
        counts[:for] == counts[:against] && (counts[:for] > 0)
      end

      def text_for(party)
        return VoteResult.human_attribute_name 'voted_for' if party_for?(party)
        return VoteResult.human_attribute_name 'voted_against' if party_against?(party)
        return VoteResult.human_attribute_name 'voted_neither' unless party_participated?(party)
        return VoteResult.human_attribute_name 'voted_neither' if party_split?(party)

        raise 'Vote counting error.'
      end

      def key_for(party)
        return :for if party_for?(party)
        return :against if party_against?(party)
        return :unknown unless party_participated?(party)
        return :split if party_split?(party)

        raise 'Vote counting error.'
      end

      def unanimous?
        @unanimous ||= parties.map { |party| key_for(party) }.reject { |k| k == :split }.uniq.size == 1
      end

      def parties
        @party_counts.keys.select { |e| party_participated?(e) }
      end

      def groups
        res = {for: [], against: []}

        parties.each do |party|
          res[:for] << party if party_for?(party)
          res[:against] << party if party_against?(party)
        end

        res[:for].sort!
        res[:against].sort!

        res
      end

      private

      def compute_party_counts(vote)
        time = vote.time
        party_results = vote.vote_results.includes({representative: {party_memberships: :party}}).group_by { |r| r.representative.party_at(time) }

        res = {}

        party_results.each do |party, vote_results|
          res[party] = counts_for(vote_results)
        end

        res
      end

      def counts_for(vote_results)
        res = Hash.new(0)
        vote_results.each do |vote_result|
          res[vote_result.state] += 1
        end

        res
      end

    end
  end
end