AyuntamientoMadrid/transparencia

View on GitHub
app/models/person.rb

Summary

Maintainability
D
2 days
Test Coverage
require 'sorting_name_calculator'

class Person < ActiveRecord::Base
  extend FriendlyId
  friendly_id :name, use: :slugged

  include ParseDataRows

  self.record_timestamps = false

  has_attached_file :portrait,
                    styles: { medium: "245x245>", thumb: "100x100>" },
                    url: "/system/:class/:style/:friendly.:extension",
                    default_url: "/missing.png",
                    use_timestamp: false
  validates_attachment_content_type :portrait, content_type: %r{\Aimage\/.*\z}

  Paperclip.interpolates :friendly do |attachment, style|
    attachment.instance.friendly_id
  end

  belongs_to :party
  belongs_to :hidden_by,   class_name: 'Administrator'
  belongs_to :unhidden_by, class_name: 'Administrator'

  has_many :assets_declarations,     -> { sort_for_list }, dependent: :destroy
  has_many :activities_declarations, -> { sort_for_list }, dependent: :destroy

  accepts_nested_attributes_for :activities_declarations
  accepts_nested_attributes_for :assets_declarations

  scope :unhidden, -> { where('people.hidden_at IS NULL OR (people.unhidden_at IS NOT NULL AND people.unhidden_at > people.hidden_at)') }
  scope :hidden,   -> { where('people.hidden_at IS NOT NULL AND (people.unhidden_at IS NULL OR people.unhidden_at < people.hidden_at)') }

  def self.job_levels
    %W{councillor director temporary_worker public_worker spokesperson labour}
  end

  def self.orders
    %w{party_name area name_initial}
  end

  validates :first_name, presence: true
  validates :last_name,  presence: true
  validates :role,       presence: true
  validates :job_level,  presence: true
  validates :party,      presence: true, if: :councillor?
  validates :job_level,  inclusion: { in: Person.job_levels }

  scope :councillors,       -> { where(job_level: 'councillor') }
  scope :directors,         -> { where(job_level: 'director') }
  scope :temporary_workers, -> { where(job_level: 'temporary_worker') }
  scope :public_workers,    -> { where(job_level: 'public_worker') }
  scope :spokespeople,      -> { where(job_level: 'spokesperson') }
  scope :labours,           -> { where(job_level: 'labour') }
  scope :working,           -> { where(leaving_date: nil) }
  scope :not_working,       -> { where.not(leaving_date: nil).order(:leaving_date) }

  scope :unhidden, -> { where('people.hidden_at IS NULL OR (people.unhidden_at IS NOT NULL AND people.unhidden_at > people.hidden_at)') }
  scope :hidden,   -> { where('people.hidden_at IS NOT NULL AND (people.unhidden_at IS NULL OR people.unhidden_at < people.hidden_at)') }

  after_initialize :initialize_profile
  before_validation :calculate_sorting_name
  after_save :refresh_party_councillors_count
  after_destroy :refresh_party_councillors_count

  def not_working?
    !working?
  end

  def working?
    leaving_date.blank?
  end

  def profile
    self[:profile] = {} if self[:profile].nil?
    self[:profile]
  end

  def should_display_profile?
    # non-working councillors should not display their profile
    !councillor? || working?
  end

  def should_display_declarations?
    councillor? || director?
  end

  def should_display_calendar?
    working?
  end

  def councillor?
    job_level == 'councillor'
  end

  def director?
    job_level == 'director'
  end

  def temporary_worker?
    job_level == 'temporary_worker'
  end

  def public_worker?
    job_level == 'public_worker'
  end

  def spokesperson?
    job_level == 'spokesperson'
  end

  def labour?
    job_level == 'labour'
  end

  def studies
    parse_data_rows(profile, :studies)
  end

  def studies_attributes=(attributes)
    profile['studies'] = clean_attributes attributes
  end

  def studies_comment
    profile['studies_comment']
  end

  def studies_comment=(comment)
    profile['studies_comment'] = comment
  end

  def has_studies?
    profile['studies'].present? || profile['studies_comment'].present?
  end

  def has_courses?
    profile['courses'].present? || profile['courses_comment'].present?
  end

  def courses
    parse_data_rows(profile, :courses)
  end

  def courses_attributes=(attributes)
    profile['courses'] = clean_attributes attributes
  end

  def courses_comment
    profile['courses_comment']
  end

  def courses_comment=(comment)
    profile['courses_comment'] = comment
  end

  def languages
    parse_data_rows(profile, :languages)
  end

  def languages_attributes=(attributes)
    profile['languages'] = clean_attributes attributes
  end

  def public_jobs
    parse_data_rows(profile, :public_jobs)
  end

  def public_jobs_attributes=(attributes)
    profile['public_jobs'] = clean_attributes attributes
  end

  def private_jobs
    parse_data_rows(profile, :private_jobs)
  end

  def private_jobs_attributes=(attributes)
    profile['private_jobs'] = clean_attributes attributes
  end

  def has_career?
    profile['public_jobs'].try(:any?) ||
    profile['private_jobs'].try(:any?) ||
    profile['public_posts'].try(:any?) ||
    career_comment.present? ||
    public_jobs_level.present? ||
    public_jobs_body.present? ||
    public_jobs_start_year.present?
  end

  def career_comment
    profile['career_comment']
  end

  def career_comment=(comment)
    profile['career_comment'] = comment
  end

  def public_jobs_level
    profile['public_jobs_level']
  end

  def public_jobs_level=(level)
    profile['public_jobs_level'] = level
  end

  def public_jobs_body
    profile['public_jobs_body']
  end

  def public_jobs_body=(body)
    profile['public_jobs_body'] = body
  end

  def public_jobs_start_year
    profile['public_jobs_start_year']
  end

  def public_jobs_start_year=(start_year)
    profile['public_jobs_start_year'] = start_year
  end

  def political_posts
    parse_data_rows(profile, :political_posts)
  end

  def political_posts_attributes=(attributes)
    profile['political_posts'] = clean_attributes attributes
  end

  def political_posts_comment
    profile['political_posts_comment']
  end

  def political_posts_comment=(comment)
    profile['political_posts_comment'] = comment
  end

  def publications
    profile['publications']
  end

  def publications=(pub)
    profile['publications'] = pub
  end

  def special_mentions
    profile['special_mentions']
  end

  def special_mentions=(mentions)
    profile['special_mentions'] = mentions
  end

  def teaching_activity
    profile['teaching_activity']
  end

  def teaching_activity=(activity)
    profile['teaching_activity'] = activity
  end

  def other
    profile['other']
  end

  def other=(o)
    profile['other'] = o
  end

  def add_study(description, entity, start_year, end_year)
    add_item('studies', description, entity, start_year, end_year)
  end

  def add_course(description, entity, start_year, end_year)
    add_item('courses', description, entity, start_year, end_year)
  end

  def add_language(name, level)
    return if name.blank?
    self.profile['languages'] ||= []
    self.profile['languages'] << { name: name, level: level }
  end

  def add_public_job(description, entity, start_year, end_year)
    add_item('public_jobs', description, entity, start_year, end_year)
  end

  def add_private_job(description, entity, start_year, end_year)
    add_item('private_jobs', description, entity, start_year, end_year)
  end

  def add_political_post(description, entity, start_year, end_year)
    add_item('political_posts', description, entity, start_year, end_year)
  end

  # Regenerate slug if name changes
  def should_generate_new_friendly_id?
    slug.blank? || name_changed?
  end

  def party_name
    party.try(:name) || I18n.t('people.no_party')
  end

  def name_initial
    sorting_name[0].upcase
  end

  def area
    super || I18n.t('people.no_area')
  end

  def name
    "#{first_name} #{last_name}"
  end

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

  def name_changed?
    first_name_changed? || last_name_changed?
  end

  def self.grouped_by_name_initial
    order(:sorting_name)
      .group_by(&:name_initial)
      .map { |k, v| [k, v.sort_by(&:sorting_name)] }
      .to_h
  end

  def self.grouped_by_party
    sorted_party_ids = Party.all.order(councillors_count: :desc).pluck(:id)
    Hash[self.includes(:party)
             .order(leaving_date: :desc, councillor_code: :asc)
             .group_by(&:party)
             .sort_by{ |party, v| sorted_party_ids.index(party.id) }
    ]
  end

  def hide(hidden_by, hidden_reason, hidden_at = DateTime.current)
    attrs = { unhidden_at: nil,
              unhidden_by_id: nil,
              hidden_at: hidden_at,
              unhidden_reason: nil,
              hidden_by_id: hidden_by.id,
              hidden_reason: hidden_reason }
    attrs[:leaving_date] = hidden_at if councillor?
    self.update(attrs)
  end

  def unhide(unhidden_by, unhidden_reason, unhidden_at = DateTime.current)
    self.update(hidden_at: nil,
                hidden_by_id: nil,
                leaving_date: nil,
                unhidden_at: unhidden_at,
                unhidden_by_id: unhidden_by.id,
                unhidden_reason: unhidden_reason)
  end

  def hidden?
    hidden_at.present? && (unhidden_at.blank? || unhidden_at < hidden_at)
  end

  def unhidden?
    hidden_at.blank? || (unhidden_at.present? && unhidden_at > hidden_at)
  end

  %i(first second third fourth).each_with_index do |ordinal, index|
    %i(description entity start_year end_year).each do |field|
      define_method "#{ordinal}_study_#{field}" do
        studies[index].try(field)
      end

      define_method "#{ordinal}_course_#{field}" do
        courses[index].try(field)
      end

      define_method "#{ordinal}_public_job_#{field}" do
        public_jobs[index].try(field)
      end

      define_method "#{ordinal}_private_job_#{field}" do
        private_jobs[index].try(field)
      end

      define_method "#{ordinal}_political_post_#{field}" do
        political_posts[index].try(field)
      end
    end
  end

  { english: 'Inglés', french: 'Francés',
    german: 'Alemán', italian: 'Italiano' }.each do |symbol, name|
    define_method "language_#{symbol}_level" do
      languages.find { |l| l[:name].to_s == name }.try(:[], :level)
    end
  end

  def language_other_name
    languages.find { |l| !%w(Inglés Francés Alemán Italiano).include?(l.name) }.try(:name)
  end

  def language_other_level
    languages.find { |l| !%w(Inglés Francés Alemán Italiano).include?(l.name) }.try(:level)
  end

  def job_level_code
    return 'C' if councillor?
    return 'D' if director?
    return 'E' if temporary_worker?
    return 'F' if public_worker?
    return 'L' if labour?
    return 'V' if spokesperson?
  end

  private

    def initialize_profile
      self.profile ||= {}
    end

    def add_item(collection, description, entity, start_year, end_year)
      return if description.blank?
      self.profile[collection] ||= []
      self.profile[collection] << { description: description, entity: entity, start_year: start_year, end_year: end_year }
    end

    def clean_attributes(attributes)
      attributes.values.select{ |a| a.values.any?(&:present?) }
    end

    def calculate_sorting_name
      self.sorting_name = SortingNameCalculator.calculate(first_name, last_name)
    end

    def refresh_party_councillors_count
      party.refresh_councillors_count if party.present?
    end

end