<commit_before># == Schema Information
# Schema version: 20080705164959
#
# Table name: events
#
#  id              :integer         not null, primary key
#  title           :string(255)
#  description     :text
#  start_time      :datetime
#  venue_id        :integer
#  url             :string(255)
#  created_at      :datetime
#  updated_at      :datetime
#  source_id       :integer
#  duplicate_of_id :integer
#  end_time        :datetime
#

# == Event
#
# A model representing a calendar event.
class Event < ActiveRecord::Base
  Tag # this class uses tagging. referencing the Tag class ensures that has_many_polymorphs initializes correctly across reloads.

  # Names of columns and methods to create Solr indexes for
  INDEXABLE_FIELDS = \
    %w(
      title
      description
      url
      duplicate_for_solr
      start_time_for_solr
      end_time_for_solr
      event_title_for_solr
      venue_title_for_solr
      text_for_solr
      tag_list
    ).map(&:to_sym)

  unless RAILS_ENV == 'test'
    acts_as_solr :fields => INDEXABLE_FIELDS
  end

  # last Time representable in certain operating systems is Jan 18 2038, local time
  # TODO rewrite SQL to eliminate the need for this constant
  END_OF_TIME = Time.local(2038, 01, 18).yesterday.end_of_day.utc

  # Associations
  belongs_to :venue
  belongs_to :source

  # Triggers
  before_validation :normalize_url!

  # Validations
  validates_presence_of :title, :start_time
  validate :end_time_later_than_start_time
  validates_format_of :url,
    :with => /(http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?/,
    :allow_blank => true,
    :allow_nil => true

  # Duplicates
  include DuplicateChecking
  duplicate_checking_ignores_attributes    :source_id
  duplicate_squashing_ignores_associations :tags

  # Named scopes
  named_scope :masters,
    :conditions => ["events.duplicate_of_id IS NULL"],
    :include => [:source, :venue, :tags, :taggings]

  #---[ Overrides ]-------------------------------------------------------

  # Return the title but strip out any whitespace.
  def title
    # TODO Generalize this code so we can use it on other attributes in the different model classes. The solution should use an #alias_method_chain to make sure it's not breaking any explicit overrides for an attribute.
    return read_attribute(:title).ergo.strip
  end

  #---[ Queries ]---------------------------------------------------------

  # Associate this event with the +venue+. The +venue+ can be given as a Venue
  # instance, an ID, or a title.
  def associate_with_venue(venue)
    venue = \
      case venue
      when Venue    then venue
      when NilClass then nil
      when String   then Venue.find_or_initialize_by_title(venue)
      when Fixnum   then Venue.find(venue)
      else raise TypeError, "Unknown type: #{venue.class}"
      end

    if venue && ((self.venue && self.venue.title != venue.title) || (!self.venue))
      # Set venue if it was nil or had a different title
      self.venue = venue.duplicate? ? venue.duplicate_of : venue
    elsif !venue && self.venue
      # Clear the event's venue field
      self.venue = nil
    end

    return self.venue
  end

  # Returns groups of records for the overview screen.
  #
  # The data structure returned maps time names to arrays of event records:
  #   {
  #     :today => [...],
  #     :tomorrow => [...],
  #     :later => [...],
  #   }
  def self.select_for_overview
    today = Time.today.beginning_of_day
    tomorrow = today + 1.day
    after_tomorrow = tomorrow + 1.day
    future_cutoff = today + 2.weeks

    times_to_events = {
      :today    => [],
      :tomorrow => [],
      :later    => [],
    }

    # find all events between today and future_cutoff, sorted by start_time
    # includes events any part of which occurs on or after today through on or after future_cutoff
    overview_events = Event.find_by_dates(today.utc, future_cutoff, :order => :start_time)
    overview_events.each do |event|
      if event.start_time < tomorrow
        times_to_events[:today]    << event
      elsif event.start_time >= tomorrow && event.start_time < after_tomorrow
        times_to_events[:tomorrow] << event
      else
        times_to_events[:later]    << event
      end
    end

    return times_to_events
  end

  # Returns an Array of non-duplicate future Event instances.
  # where "future" means any part of an event occurs today or later
  # Options:
  # * :order => How to sort events. Defaults to :start_time.
  # * :venue => Which venue to display events for. Defaults to all.
  def self.find_future_events(opts={})
    order = opts[:order] || :start_time
    venue = opts[:venue]
    # TODO eliminate need for END_OF_TIME constant
    Event.find_by_dates(Time.now.beginning_of_day.utc, END_OF_TIME, :order => order, :venue => venue)
  end

  # Returns an Array of non-duplicate Event instances in a date range
  # includes event if any part of the event is (on or after the start) and (on or before the end)
  # Options:
  # * :order => How to sort events. Defaults to :start_time.
  # * :venue => Which venue to display events for. Defaults to all.
  def self.find_by_dates(start_of_range, end_of_range, opts={})
    # TODO make it possible to set 'end_of_range' to nil, and eliminate the END_OF_TIME constant and its use in find_future_events.
    start_of_range = Time.parse(start_of_range.to_s) if start_of_range.is_a?(Date)
    end_of_range = Time.parse(end_of_range.to_s).end_of_day if end_of_range.is_a?(Date)
    order = opts [:order] || :start_time

    # an event with an end_time is out of range if
    # its start_time is after the end of range OR its end_time is before the start of the range
    # otherwise (an event with an end_time), event is in range if its start_time is in range
    # Query is complex because SQL has tertiary logic for NUll and end_time may be NULL
    conditions_sql = <<-HERE
      (NOT (start_time > :end_of_range OR end_time < :start_of_range ) OR
      (start_time >= :start_of_range AND start_time <= :end_of_range) )
    HERE

    conditions_vars = {
      :start_of_range => start_of_range.utc,
      :end_of_range => end_of_range.utc }

    if venue = opts[:venue]
      conditions_sql << " AND venues.id == :venue"
      conditions_vars[:venue] = venue.id
    end

    return self.masters.find(:all,
      :conditions => [conditions_sql, conditions_vars],
      :order => order)
  end

  # Return Hash of Events grouped by the +type+.
  def self.find_duplicates_by_type(type='title')
    if type == 'na'
      return { [] => self.find_future_events }<commit_msg>Refactored find_by_dates to get rid of END_OF_TIME constant.  Closes Issue266.<commit_after># == Schema Information
# Schema version: 20080705164959
#
# Table name: events
#
#  id              :integer         not null, primary key
#  title           :string(255)
#  description     :text
#  start_time      :datetime
#  venue_id        :integer
#  url             :string(255)
#  created_at      :datetime
#  updated_at      :datetime
#  source_id       :integer
#  duplicate_of_id :integer
#  end_time        :datetime
#

# == Event
#
# A model representing a calendar event.
class Event < ActiveRecord::Base
  Tag # this class uses tagging. referencing the Tag class ensures that has_many_polymorphs initializes correctly across reloads.

  # Names of columns and methods to create Solr indexes for
  INDEXABLE_FIELDS = \
    %w(
      title
      description
      url
      duplicate_for_solr
      start_time_for_solr
      end_time_for_solr
      event_title_for_solr
      venue_title_for_solr
      text_for_solr
      tag_list
    ).map(&:to_sym)

  unless RAILS_ENV == 'test'
    acts_as_solr :fields => INDEXABLE_FIELDS
  end

  # Associations
  belongs_to :venue
  belongs_to :source

  # Triggers
  before_validation :normalize_url!

  # Validations
  validates_presence_of :title, :start_time
  validate :end_time_later_than_start_time
  validates_format_of :url,
    :with => /(http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?/,
    :allow_blank => true,
    :allow_nil => true

  # Duplicates
  include DuplicateChecking
  duplicate_checking_ignores_attributes    :source_id
  duplicate_squashing_ignores_associations :tags

  # Named scopes
  named_scope :masters,
    :conditions => ["events.duplicate_of_id IS NULL"],
    :include => [:source, :venue, :tags, :taggings]

  #---[ Overrides ]-------------------------------------------------------

  # Return the title but strip out any whitespace.
  def title
    # TODO Generalize this code so we can use it on other attributes in the different model classes. The solution should use an #alias_method_chain to make sure it's not breaking any explicit overrides for an attribute.
    return read_attribute(:title).ergo.strip
  end

  #---[ Queries ]---------------------------------------------------------

  # Associate this event with the +venue+. The +venue+ can be given as a Venue
  # instance, an ID, or a title.
  def associate_with_venue(venue)
    venue = \
      case venue
      when Venue    then venue
      when NilClass then nil
      when String   then Venue.find_or_initialize_by_title(venue)
      when Fixnum   then Venue.find(venue)
      else raise TypeError, "Unknown type: #{venue.class}"
      end

    if venue && ((self.venue && self.venue.title != venue.title) || (!self.venue))
      # Set venue if it was nil or had a different title
      self.venue = venue.duplicate? ? venue.duplicate_of : venue
    elsif !venue && self.venue
      # Clear the event's venue field
      self.venue = nil
    end

    return self.venue
  end

  # Returns groups of records for the overview screen.
  #
  # The data structure returned maps time names to arrays of event records:
  #   {
  #     :today => [...],
  #     :tomorrow => [...],
  #     :later => [...],
  #   }
  def self.select_for_overview
    today = Time.today
    tomorrow = today + 1.day
    after_tomorrow = tomorrow + 1.day
    future_cutoff = today + 2.weeks

    times_to_events = {
      :today    => [],
      :tomorrow => [],
      :later    => [],
    }

    # find all events between today and future_cutoff, sorted by start_time
    # includes events any part of which occurs on or after today through on or after future_cutoff
    overview_events = Event.find_by_dates(today.utc, future_cutoff, :order => :start_time)
    overview_events.each do |event|
      if event.start_time < tomorrow
        times_to_events[:today]    << event
      elsif event.start_time >= tomorrow && event.start_time < after_tomorrow
        times_to_events[:tomorrow] << event
      else
        times_to_events[:later]    << event
      end
    end

    return times_to_events
  end

  # Returns an Array of non-duplicate future Event instances.
  # where "future" means any part of an event occurs today or later
  # Options:
  # * :order => How to sort events. Defaults to :start_time.
  # * :venue => Which venue to display events for. Defaults to all.
  def self.find_future_events(opts={})
    order = opts[:order] || :start_time
    venue = opts[:venue]
    Event.find_by_dates(Time.today.utc, nil, :order => order, :venue => venue)
  end

  # Returns an Array of non-duplicate Event instances in a date range
  # includes event if any part of the event is (on or after the start) and (on or before the end)
  # Options:
  # * :order => How to sort events. Defaults to :start_time.
  # * :venue => Which venue to display events for. Defaults to all.
  def self.find_by_dates(start_of_range, end_of_range, opts={})
    start_of_range = Time.parse(start_of_range.to_s) if start_of_range.is_a?(Date)
    end_of_range = Time.parse(end_of_range.to_s).end_of_day if end_of_range.is_a?(Date)
    order = opts [:order] || :start_time

    # treat nil end_of_range as having no end cutoff
    # Otherwise event is within range if (a) its start_time is in range, or
    #  (b) if it has an end_time, it's not out of range:
    #  event with an end_time is out of range if
    #  its start_time is after the end of range OR its end_time is before the start of the range
    #  checking for NOT out of range takes care of tertiary NULL logic for NULL end_time
    conditions_sql = <<-HERE
      (:end_of_range IS NULL AND
        (start_time >= :start_of_range) OR
        (end_time IS NOT NULL AND end_time >= :start_of_range) )
      OR
      (:end_of_range IS NOT NULL AND
        (start_time >= :start_of_range AND start_time <= :end_of_range) OR
        (NOT (start_time > :end_of_range OR end_time < :start_of_range) ) )
    HERE

    conditions_vars = {
      :start_of_range => start_of_range.utc,
      :end_of_range => end_of_range.ergo.utc }

    if venue = opts[:venue]
      conditions_sql << " AND venues.id == :venue"
      conditions_vars[:venue] = venue.id
    end

    return self.masters.find(:all,
      :conditions => [conditions_sql, conditions_vars],
      :order => order)
  end

  # Return Hash of Events grouped by the +type+.
  def self.find_duplicates_by_type(type='title')
    if type == 'na'
      return { [] => self.find_future_events }
{'fraction_non_alphanumeric': 0.11026615969581749, 'fraction_numerical': 0.005399239543726236, 'mean_word_length': 2.7628040057224608, 'pattern_counts': {'":': 0, '<': 33, '<?xml version=': 0, '>': 59, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '7205466', 'n_tokens_mistral': 4226, 'n_tokens_neox': 4091, 'n_words': 1278}