holderdeord/hdo-site

View on GitHub
app/models/proposition.rb

Summary

Maintainability
B
5 hrs
Test Coverage
class Proposition < ActiveRecord::Base
  include Hdo::Search::Index

  settings(SearchSettings.default) {
    mappings {
      indexes :description,                           type: :string, analyzer: SearchSettings.default_analyzer
      indexes :plain_body,                            type: :string, analyzer: SearchSettings.default_analyzer
      indexes :simple_description,                    type: :string, analyzer: SearchSettings.default_analyzer
      indexes :simple_body,                           type: :string, analyzer: SearchSettings.default_analyzer
      indexes :on_behalf_of,                          type: :string
      indexes :vote_time,                             type: :date
      indexes :parliament_session_name,               type: :string,  index: :not_analyzed
      indexes :id,                                    type: :integer, index: :not_analyzed
      indexes :interesting,                           type: :boolean
      indexes :starred,                               type: :boolean
      indexes :committee_names,                       type: :string,  index: :not_analyzed
      indexes :category_names,                        type: :string,  index: :not_analyzed
      indexes :parliament_issue_type_names,           type: :string,  index: :not_analyzed
      indexes :parliament_issue_document_group_names, type: :string,  index: :not_analyzed
      indexes :issue_ids,                             type: :integer, index: :not_analyzed

      indexes :votes do
        indexes :slug,    type: :string, index: :not_analyzed
        indexes :enacted, type: :boolean
      end

      indexes :proposers do
        indexes :external_id, type: :string, index: :not_analyzed
        indexes :name,        type: :string, index: :not_analyzed
      end
    }
  }

  add_index_callbacks partial_update: false

  update_index_on_change_of :votes, has_many: true
  update_index_on_change_of :parliament_issues, has_many: true
  update_index_on_change_of :proposition_connections

  attr_accessible :description, :on_behalf_of, :body, :representative_id,
                  :simple_description, :simple_body,
                  :interesting, :starred

  has_and_belongs_to_many :votes, uniq: true
  has_many :proposition_connections, dependent: :destroy
  has_many :issues, through: :proposition_connections
  has_many :parliament_issues, through: :votes
  has_many :proposition_endorsements, dependent: :destroy

  validates :body, presence: true
  validates :simple_description, length: {minimum: 1, maximum: 255}, allow_nil: true
  validates :simple_body, length: {minimum: 1}, allow_nil: true

  validates_uniqueness_of :external_id, allow_nil: true # https://github.com/holderdeord/hdo-site/issues/138

  scope :interesting,  -> { where(interesting: true) }
  scope :vote_ordered, -> { includes(:votes).order('votes.time DESC') }

  def plain_body
    Nokogiri::HTML.parse(body).xpath('//text()').map { |e| e.text.strip }.join(' ')
  end

  TITLE_RULES = {
    /^«(.+?)»$/ => '\\1',
    /^Stortinget (ber|anmoder) (regjeringen|regjeringa|stortingets presidentskap)( om å)?(, )?/i => '',
    /^Stortinget samtykker i/ => 'Samtykke i',
    /^(I statsbudsjettet for \d+ gjøres følgende endringer\b).+/im => '\\1'
  }

  def auto_title
    title = plain_body.gsub("\n", " ")

    TITLE_RULES.each do |rx, replacement|
      title.sub!(rx, replacement)
    end

    if title =~ /^.+? [–] (.+?) [–] (vedlegges protokollen|vert vedlagt protokollen|bifalles ikke|vedtas ikke)/
      desc = $1
      action = $2

      desc_words = desc.split(" ")
      new_desc = "#{UnicodeUtils.downcase(desc_words[0])} #{desc_words[1..-1].join(" ")}"

      title = case action
              when 'bifalles ikke'
                "Ikke bifalle #{new_desc}"
              when 'vedtas ikke'
                "Ikke vedta #{new_desc}"
              else
                if desc[0] =~ /[A-ZÆÅØ]/u
                  %{Legge «#{desc}» ved protokollen}
                else
                  %{Legge #{new_desc} ved protokollen}
                end
              end
    else
      title = title.split(/(?<!Meld|Prop|Kap|jf|nr|mill|St|pst|pr|\b[A-Z]|\d)[.:]( |$)/).first
    end


    if title
      title = "#{title}." unless title.ends_with?(".") || title.include?('følgende')
      title.strip!
      "#{UnicodeUtils.upcase title[0]}#{title[1..-1]}"
    else
      ''
    end
  end

  def vote_time
    @vote_time ||= votes.first.try(:time)
  end

  def parliament_session
    @parliament_session ||= ParliamentSession.for_date(vote_time.to_date)
  end

  def parliament_session_name
    parliament_session.try(:name)
  end

  def parliament_period
    @parliament_period ||= ParliamentPeriod.for_date(vote_time.to_date)
  end

  def parliament_period_name
    parliament_period.try(:name)
  end

  def proposers
    proposition_endorsements.map(&:proposer)
  end

  def add_proposer(proposer, params = {})
    proposition_endorsements.create!(proposer: proposer, inferred: !!params[:inferred])
  end

  def delete_proposer(proposer)
    proposition_endorsements.where(proposer_id: proposer.id, proposer_type: proposer.class.name).destroy_all
  end

  def previous
    return unless v = votes.order(:time).first
    @previous ||= self.class.joins(:votes).where("votes.time < ?", v.time).order('votes.time DESC').first
  end

  def next
    return unless v = votes.order(:time).last
    @next ||= self.class.joins(:votes).where("votes.time > ?", v.time).order('votes.time').first
  end

  def source_guess
    @source_guess ||= Hdo::Utils::PropositionSourceGuesser.parties_for("#{on_behalf_of} #{description}")
  end

  def related_propositions
    related_propositions = parliament_issues.flat_map { |e| e.votes.flat_map(&:propositions) }.uniq
    related_propositions.delete self

    related_propositions
  end

  #
  # for indexing:
  #

  def committees
    parliament_issues.map(&:committee).compact.uniq
  end

  def committee_names
    committees.map(&:name)
  end

  def categories
    parliament_issues.flat_map(&:categories).compact.uniq
  end

  def category_names
    categories.map(&:human_name).compact
  end

  def parliament_issue_type_names
    parliament_issues.map(&:issue_type_name).compact.uniq
  end

  def parliament_issue_document_group_names
    parliament_issues.map(&:document_group_name).compact.uniq
  end

  def issue_ids
    issues.map(&:id)
  end

  def vote_enacted
    votes.first.try(:enacted?)
  end

  def as_indexed_json(options = nil)
    methods = [
      :plain_body,
      :committee_names,
      :category_names,
      :proposers,
      :parliament_issue_type_names,
      :parliament_issue_document_group_names,
      :issue_ids,
      :auto_title,
      :vote_enacted
    ]

    methods += [:parliament_session_name, :parliament_period_name, :vote_time] if votes.any?

    as_json methods: methods,
            include: {votes: {only: [:slug, :enacted]} },
            only:    [:description, :on_behalf_of, :id,
                      :simple_description, :simple_body, :interesting, :starred]
  end
end