rubyberlin/cfp-app

View on GitHub
app/models/event.rb

Summary

Maintainability
B
5 hrs
Test Coverage
class Event < ApplicationRecord
  has_many :teammates, dependent: :destroy
  has_many :staff, through: :teammates, source: :user
  has_many :proposals, dependent: :destroy
  has_many :speakers
  has_many :rooms, dependent: :destroy
  has_many :tracks, dependent: :destroy
  has_many :time_slots, dependent: :destroy
  has_many :program_sessions, dependent: :destroy
  has_many :session_formats, dependent: :destroy
  has_many :taggings, through: :proposals
  has_many :ratings, through: :proposals

  has_many :public_session_formats, -> { where(public: true) }, class_name: 'SessionFormat'

  accepts_nested_attributes_for :proposals

  serialize :proposal_tags, Array
  serialize :review_tags, Array
  serialize :custom_fields, Array
  serialize :settings, Hash
  serialize :speaker_notification_emails, Hash

  store_accessor :speaker_notification_emails, :accept
  store_accessor :speaker_notification_emails, :reject
  store_accessor :speaker_notification_emails, :waitlist

  scope :a_to_z, -> { order('name ASC') }
  scope :closes_up, -> { order('closes_at ASC') }
  scope :live, -> { where("state = 'open' and (closes_at is null or closes_at > ?)", Time.current) }
  scope :not_draft, -> { where "state != 'draft'" }

  validates :name, presence: true
  validates :slug, presence: true, uniqueness: true
  validate :url_must_be_valid, if: :url?

  before_validation :generate_slug
  before_save :update_closes_at_if_manually_closed

  STATUSES = { open: 'open',
               draft: 'draft',
               closed: 'closed' }.freeze

  def to_param
    slug
  end

  with_options on: :update, if: :open? do
    validates :public_session_formats, presence: { message: 'A least one public session format must be defined before event can be opened.' }
    validates :guidelines, presence: { message: 'Guidelines must be defined before event can be opened.' }
  end

  def initialize_speaker_emails
    SpeakerEmailTemplate::TYPES.each do |type|
      speaker_notification_emails[type] ||= ""
    end
  end

  def remove_speaker_email_template(template)
    attr = SpeakerEmailTemplate::TYPES.find { |type| type == template }
    update_attribute(attr, "") if attr
  end

  def public_tags?
    proposal_tags.any?
  end

  def valid_proposal_tags
    proposal_tags.join(', ')
  end

  def valid_proposal_tags=(tags_string)
    self.proposal_tags = Tagging.tags_string_to_array(tags_string)
  end

  def reviewer_tags?
    review_tags.any?
  end

  def valid_review_tags
    review_tags.join(', ')
  end

  def valid_review_tags=(tags_string)
    self.review_tags = Tagging.tags_string_to_array(tags_string)
  end

  def custom_fields_string=(custom_fields_string)
    self.custom_fields = custom_fields_string_to_array(custom_fields_string)
  end

  def custom_fields_string_to_array(string)
    (string || '').split(',').map(&:strip).reject(&:blank?).uniq
  end

  def custom_fields_string
    custom_fields.join(', ')
  end

  def fields
    proposals.column_names.join(', ')
  end

  def multiple_tracks?
    tracks.any?
  end

  def multiple_public_session_formats?
    session_formats.publicly_viewable.count > 1
  end

  def generate_slug
    self.slug = name.parameterize if name.present? && slug.blank?
  end

  def to_s
    name
  end

  def draft?
    state == STATUSES[:draft]
  end

  def open?
    state == STATUSES[:open] && (closes_at.nil? || closes_at > Time.current)
  end

  def closed?
    !open? && !draft?
  end

  def past_open?
    state == STATUSES[:open] && closes_at < Time.current
  end

  def status
    if open?
      STATUSES[:open]
    elsif draft?
      STATUSES[:draft]
    else
      STATUSES[:closed]
    end
  end

  def unmet_requirements_for_scheduling
    missing_prereqs = []

    missing_prereqs << "Event must have a start date" unless start_date
    missing_prereqs << "Event must have a end date" unless end_date

    missing_prereqs << "Event must have at least one room" if rooms.empty?

    missing_prereqs
  end

  def incomplete_checklist_items
    missing_items = []

    missing_items << "Event must have a url" if url.blank?
    missing_items << "Event must have a start date" unless start_date
    missing_items << "Event must have an end date" unless end_date
    missing_items << "Event must have a contact email" if contact_email.blank?
    missing_items << "Event must have a CFP closes at date set for a future date" unless closes_at && (closes_at > Time.current)
    missing_items << "Event must have at least one public session format" if public_session_formats.blank?
    missing_items << "Event must have guidelines" if guidelines.blank?

    missing_items
  end

  def open_cfp
    if incomplete_checklist_items.empty?
      update_attribute(:state, STATUSES[:open])
      true
    else
      errors.add(incomplete_checklist_items.join("; "))
      false
    end
  end

  def archive
    update_attribute(:archived, true) if current?
  end

  def unarchive
    update_attribute(:archived, false) if archived?
  end

  def current?
    !archived?
  end

  def cfp_opens
    opens_at.try(:to_s, :long_with_zone)
  end

  def cfp_closes
    closes_at.try(:to_s, :long_with_zone)
  end

  def conference_date(conference_day)
    start_date + (conference_day - 1).days
  end

  def url_must_be_valid
    uri = URI.parse(url)
    unless uri.is_a?(URI::HTTP) || uri.is_a?(URI::HTTPS)
      errors.add(:url, "must start with http:// or https://")
    end
  rescue URI::InvalidURIError
    errors.add(:url, "must be valid")
  end

  def stats
    @stats ||= EventStats.new(self)
  end

  def days
    (end_date.to_date - start_date.to_date).to_i + 1
  end

  def mention_names
    teammates.pluck(:mention_name)
  end

  private

  def update_closes_at_if_manually_closed
    if changes.key?(:state) && changes[:state] == [STATUSES[:open], STATUSES[:closed]]
      self.closes_at = DateTime.now
    end
  end
end

# == Schema Information
#
# Table name: events
#
#  id                          :integer          not null, primary key
#  name                        :string
#  slug                        :string
#  url                         :string
#  contact_email               :string
#  state                       :string           default("draft")
#  archived                    :boolean          default(FALSE)
#  opens_at                    :datetime
#  closes_at                   :datetime
#  start_date                  :datetime
#  end_date                    :datetime
#  info                        :text
#  guidelines                  :text
#  settings                    :text
#  proposal_tags               :text
#  review_tags                 :text
#  custom_fields               :text
#  speaker_notification_emails :text             default({:accept=>"", :reject=>"", :waitlist=>""})
#  created_at                  :datetime
#  updated_at                  :datetime
#
# Indexes
#
#  index_events_on_slug  (slug)
#