hpi-swt2/workshop-portal

View on GitHub
app/models/event.rb

Summary

Maintainability
A
2 hrs
Test Coverage
# == Schema Information
#
# Table name: events
#
#  id               :integer          not null, primary key
#  name             :string
#  description      :string
#  max_participants :integer
#  date_ranges      :Collection
#  active           :boolean
#  created_at       :datetime         not null
#  updated_at       :datetime         not null
#  application_status_locked  :boolean
#

class Event < ActiveRecord::Base
  UNREASONABLY_LONG_DATE_SPAN = 300

  has_many :application_letters
  has_many :agreement_letters
  has_many :date_ranges
  accepts_nested_attributes_for :date_ranges

  # Returns all participants for this event in following order:
  # 1. All participants that have to submit an letter of agreement but did not yet do so, ordered by name.
  # 2. All participants that have to submit an letter of agreement and did do so, ordered by name.
  # 3. All participants that do not have to submit an letter of agreement, ordered by name.
  #
  # @param none
  # @return [Array<User>] the event's participants in that order.
  def participants_by_agreement_letter
    @participants = self.participants
    @participants.sort { |x, y| self.compare_participants_by_agreement(x,y) }
  end

  validates :max_participants, numericality: { only_integer: true, greater_than: 0 }
  validate :has_date_ranges
  validates_presence_of :application_deadline
  validate :application_deadline_before_start_of_event


  # @return the minimum start_date over all date ranges
  def start_date
    (date_ranges.min { |a,b| a.start_date <=> b.start_date }).start_date
  end

  # @return the minimum end_date over all date ranges
  def end_date
    (date_ranges.max { |a,b| a.end_date <=> b.end_date }).end_date
  end

  # @return whether this event appears unreasonably long as defined by
  #         the corresponding constant
  def unreasonably_long
    end_date - start_date > Rails.configuration.unreasonably_long_event_time_span
  end

  # validation function on whether we have at least one date range
  def has_date_ranges
    errors.add(:date_ranges, I18n.t('date_range.errors.no_timespan')) if date_ranges.blank?
  end

  #validate that application deadline is before the start of the event
  def application_deadline_before_start_of_event
    errors.add(:application_deadline, I18n.t('events.errors.application_deadline_before_start_of_event')) if application_deadline.present? && !date_ranges.blank? && application_deadline > start_date
  end

  # Returns the participants whose application for this Event has been accepted
  #
  # @param none
  # @return [Array<User>] the event's participants
  def participants
    accepted_applications = application_letters.where(status: ApplicationLetter.statuses[:accepted])
    accepted_applications.collect { |a| a.user }
  end

  # Returns the agreement letter a user submitted for this event
  #
  # @param user [User] the user whose agreement letter we want
  # @return [AgreementLetter, nil] the user's agreement letter or nil
  def agreement_letter_for(user)
    self.agreement_letters.where(user: user).take
  end

  enum kind: [ :workshop, :camp ]

  # Returns whether all application_letters are classified or not
  #
  # @param none
  # @return [Boolean] if status of all application_letters is not pending
  def applications_classified?
    application_letters.all? { |application_letter| application_letter.status != 'pending' }
  end

  # Returns a string of all email addresses of accepted applications
  #
  # @param none
  # @return [String] Concatenation of all email addresses of accepted applications, seperated by ','
  def email_adresses_of_accepted_applicants
    accepted_applications = application_letters.where(status: ApplicationLetter.statuses[:accepted])
    accepted_applications.map{ |application_letter| application_letter.user.email }.join(',')
  end

  # Returns a string of all email addresses of rejected applications
  #
  # @param none
  # @return [String] Concatenation of all email addresses of rejected applications, seperated by ','
  def email_adresses_of_rejected_applicants
    rejected_applications = application_letters.where(status: ApplicationLetter.statuses[:rejected])
    rejected_applications.map{ |applications_letter| applications_letter.user.email }.join(',')
  end

  # Returns a new acceptance email
  #
  # @param none
  # @return [Email] new acceptance email
  def generate_acceptances_email
    email = Email.new
    email.hide_recipients = false
    email.recipients = email_adresses_of_accepted_applicants
    email.reply_to = 'workshop.portal@hpi.de'
    email.subject = ''
    email.content = ''
    return email
  end

  # Returns a new rejection email
  #
  # @param none
  # @return [Email] new rejection email
  def generate_rejections_email
    email = Email.new
    email.hide_recipients = false
    email.recipients = email_adresses_of_rejected_applicants
    email.reply_to = 'workshop.portal@hpi.de'
    email.subject = ''
    email.content = ''
    return email
  end

  # Returns the number of free places of the event, this value may be negative
  #
  # @param none
  # @return [Int] for number of free places available
  def compute_free_places
    max_participants - compute_occupied_places
  end

  # Returns the number of already occupied places of the event
  #
  # @param none
  # @return [Int] for number of occupied places
  def compute_occupied_places
    application_letters.where(status: ApplicationLetter.statuses[:accepted]).count
  end

  # Make sure any assignment coming from the controller
  # replaces all date ranges instead of adding new ones
  def date_ranges_attributes=(*args)
    self.date_ranges.clear
    super(*args)
  end

  # Make sure we add errors from our date_range children
  # to the base event object for displaying
  validate do |event|
    event.date_ranges.each do |date_range|
      next if date_range.valid?
      date_range.errors.full_messages.each do |msg|
        errors.add :date_ranges, msg
      end
    end
  end

  scope :draft_is, ->(draft) { where("draft = ?", draft) }

  protected
  # Compares two participants to achieve following order:
  # 1. All participants that have to submit an letter of agreement but did not yet do so, ordered by name.
  # 2. All participants that have to submit an letter of agreement and did do so, ordered by name.
  # 3. All participants that do not have to submit an letter of agreement, ordered by name.
  def compare_participants_by_agreement(participant1, participant2)
    if participant1.requires_agreement_letter_for_event?(self)
      if participant2.requires_agreement_letter_for_event?(self)
        return participant1.name <=> participant2.name
      end
      return 1
    end
    if participant2.requires_agreement_letter_for_event?(self)
      return -1
    end
    if participant1.agreement_letter_for_event?(self)
      if participant2.agreement_letter_for_event?(self)
        return participant1.name <=> participant2.name
      end
      return 1
    end
    if participant2.agreement_letter_for_event?(self)
      return -1
    end
    return participant1.name <=> participant2.name
  end

end