holderdeord/hdo-site

View on GitHub
app/models/representative.rb

Summary

Maintainability
B
4 hrs
Test Coverage
class Representative < ActiveRecord::Base
  devise :database_authenticatable,
         :recoverable, :rememberable, :trackable, :validatable,
         :confirmable

  attr_accessible :email, :password, :password_confirmation, :remember_me
  mount_uploader :image, RepresentativeUploader

  extend FriendlyId

  include Hdo::Search::Index
  add_index_callbacks partial_update: false

  settings(SearchSettings.default) {
    mappings do
      indexes :district do
        indexes :name, type: :string
        indexes :slug, type: :string, index: :not_analyzed
      end

      indexes :latest_party do
        indexes :name, type: :string
        indexes :slug, type: :string, index: :not_analyzed
      end

      indexes :full_name,  index: :not_analyzed
      indexes :last_name,  index: :not_analyzed
      indexes :first_name, index: :not_analyzed
      indexes :twitter_id, index: :not_analyzed
      indexes :attending,  index: :not_analyzed
      indexes :slug,       index: :not_analyzed
    end
  }

  update_index_on_change_of :district, :parties, has_many: true
  update_index_on_change_of :party_memberships

  attr_accessible :first_name, :last_name, :committees, :district,
                  :date_of_birth, :date_of_death, :twitter_id, :email,
                  :attending, :opted_out, :substitute

  default_scope order: :last_name

  belongs_to :district

  has_many :vote_results, dependent: :destroy
  has_many :votes,        through:   :vote_results

  has_many :party_memberships, dependent: :destroy
  has_many :parties,           through: :party_memberships

  has_many :committee_memberships, dependent: :destroy
  has_many :committees,            through: :committee_memberships

  has_many :questions
  has_many :answers

  has_many :proposition_endorsements, as: :proposer
  has_many :propositions, through: :proposition_endorsements

  validates_uniqueness_of :first_name, scope: :last_name # TODO: scope: :period ?!

  validates_confirmation_of :password

  validates :external_id, presence: true, uniqueness: true
  validates :email,       allow_nil: true, uniqueness: true, email: true
  validates :twitter_id,  allow_nil: true, uniqueness: true, format: /^[^@]/

  scope :attending,              -> { where(attending: true) }
  scope :with_email,             -> { where('email IS NOT NULL') }
  scope :with_twitter,           -> { where('twitter_id IS NOT NULL') }
  scope :without_twitter,        -> { where('twitter_id IS NULL') }
  scope :potentially_askable,    -> { attending.with_email }
  scope :askable,                -> { potentially_askable.where('opted_out IS NULL or opted_out IS FALSE') }
  scope :opted_out,              -> { potentially_askable.where('opted_out IS TRUE') }
  scope :registered,             -> { where('confirmed_at IS NOT NULL')}

  friendly_id :external_id, use: :slugged

  def year_of_birth
    date_of_birth.localtime.year
  end

  def unconfirmed_email
    email unless confirmed?
  end

  def only_if_unconfirmed
    pending_any_confirmation { yield }
  end

  def attempt_set_password(params)
    return false unless params && params[:password]

    attrs = {
      password:              params[:password],
      password_confirmation: params[:password_confirmation]
    }
    update_attributes(attrs)
  end

  # new function to return whether a password has been set
  def has_no_password?
    self.encrypted_password.blank?
  end

  def display_name
    "#{last_name}, #{first_name}"
  end

  def name
    "#{first_name} #{last_name}"
  end
  alias_method :full_name, :name

  def name_with_party
    # missing spec
    "#{full_name} (#{latest_party.name})"
  end

  def askable?
    attending? && !email.blank? && !opted_out
  end

  def opted_out?
    opted_out
  end

  def alternate_text
    alternate? ? I18n.t("app.yes") : I18n.t("app.no")
  end

  def current_party
    party_at Time.current
  end

  def latest_party
    membership = if party_memberships.loaded?
                   party_memberships.sort_by(&:start_date).last
                 else
                   party_memberships.order(:start_date).last
                 end

    membership && membership.party
  end

  def current_party_membership
    party_membership_at Time.current
  end

  def party_at(date)
    membership = party_membership_at(date)
    membership && membership.party
  end

  def party_membership_at(date)
    if party_memberships.loaded?
      # if all the memberships are already loaded, it's faster to check dates in code
      # a better solution would probably be to do a more clever query in Hdo::Stats::VoteScorer#party_percentages_for
      party_memberships.find { |e| e.include?(date) }
    else
      party_memberships.for_date(date).first
    end
  end

  def current_committees
    committees_at Time.current
  end

  def committees_at(date)
    memberships = committee_memberships_at(date)
    memberships.map { |e| e.committee }
  end

  def has_image?
    !image.to_s.include?("unknown")
  end

  def has_twitter?
    twitter_id.present?
  end

  def committee_memberships_at(date)
    if committee_memberships.loaded?
      committee_memberships.select { |e| e.include?(date) }
    else
      committee_memberships.for_date(date)
    end
  end

  def age
    dob = date_of_birth or return -1

    if date_of_death
      now = date_of_death
    else
      now = Date.today
    end

    now = now.to_date

    now.year - dob.year - ((now.month > dob.month || (now.month == dob.month && now.day >= dob.day)) ? 0 : 1)
  end

  def twitter_url
    "https://twitter.com/#{twitter_id}" if twitter_id
  end

  def wikidata_url
    "https://www.wikidata.org/wiki/Special:EntityData/#{wikidata_id}.json" if wikidata_id
  end

  def stats
    Hdo::Stats::RepresentativeCounts.new self.vote_results
  end

  def as_indexed_json(options = nil)
    # TODO: decompose and index wikidata?

    as_json include: [:district],
            methods: [:latest_party, :full_name],
            only: [:slug, :last_name, :first_name, :twitter_id, :attending]
  end

  private

  # for devise:
  def email_required?() false end
  def password_required?() false end
  def confirmation_required?() false end
  def postpone_email_change?() false end
end