class Event

Superclass for anything that can have results:

instructional: class or clinc practice: training session

Events have four similar, but distinct relationships to other Events:

(that's how they are in the database). But we almost always want just the child Events,
 and not the Competitions.

Changes to parent Event’s attributes are propogated to children, unless the children’s attributes are already different. See PROPOGATED_ATTRIBUTES

It’s debatable whether we need STI subclasses or not.

All notification code just supports combined TT results, and should be moved to background processing

Constants

PROPOGATED_ATTRIBUTES

Attributes

new_promoter_name[R]
new_team_name[R]

Public Class Methods

export() click to toggle source
# File app/models/export/events.rb, line 5
def Event.export
  Event.export_head
  Event.export_data
end
export_columns() click to toggle source
# File app/models/export/events.rb, line 34
def Event.export_columns
  [
    "id", "parent_id", "name", "discipline", "date", "time",
    "sanctioned_by", "type", "city", "state", "instructional", "practice",
    "postponed", "beginner_friendly", "cancelled"
  ]
end
export_data() click to toggle source
# File app/models/export/events.rb, line 16
def Event.export_data
  Base.export(export_data_sql, "events.csv")
end
export_data_sql() click to toggle source
# File app/models/export/events.rb, line 26
def Event.export_data_sql
  "SELECT #{Event.export_columns.join(",")}
   INTO OUTFILE '%s'
   FIELDS TERMINATED BY ',' OPTIONALLY ENCLOSED BY '\"'
   LINES TERMINATED BY '\\n'
   FROM events"
end
export_head() click to toggle source
# File app/models/export/events.rb, line 12
def Event.export_head
  Base.export(export_head_sql, "events.txt")
end
export_head_sql() click to toggle source
# File app/models/export/events.rb, line 20
def Event.export_head_sql
  "SELECT '#{Event.export_columns.join("','")}'
   INTO OUTFILE '%s'
   FIELDS TERMINATED BY '\\n'"
end
find_all_bar_for_discipline(discipline, year = Time.zone.today.year) click to toggle source
# File app/models/event.rb, line 149
def Event.find_all_bar_for_discipline(discipline, year = Time.zone.today.year)
  first_of_year = Date.new(year, 1, 1)
  last_of_year = Date.new(year + 1, 1, 1) - 1
    discipline_names = [discipline]
    discipline_names << 'Circuit' if discipline.downcase == 'road'
    Set.new(Event.all(
        :select => "distinct events.id, events.*",
        :conditions => [%Q{
            events.date between ? and ?
            and events.discipline in (?)
            and bar_points > 0
            }, first_of_year, last_of_year, discipline_names]
    ))

end
find_all_with_results(year = Time.zone.today.year, discipline = nil) click to toggle source

Return [weekly_series, events] that have results Honors RacingAssociation.current.show_only_association_sanctioned_races_on_calendar

# File app/models/event.rb, line 106
def Event.find_all_with_results(year = Time.zone.today.year, discipline = nil)
  # Maybe this should be its own class, since it has knowledge of Event and Result?
  first_of_year = Date.new(year, 1, 1)
  last_of_year = Date.new(year + 1, 1, 1) - 1
  
  if discipline
    discipline_names = [discipline.name]
    if discipline == Discipline['road']
      discipline_names << 'Circuit'
    end
    events = Set.new(Event.all(
        :select => "distinct events.id, events.*",
        :joins => { :races => :results },
        :include => { :parent => :parent },
        :conditions => [%Q{
            events.date between ? and ? 
            and events.discipline in (?)
            }, first_of_year, last_of_year, discipline_names]
    ))
    
  else
    events = Set.new(Event.all(
        :select => "distinct events.id, events.*",
        :joins => { :races => :results },
        :include => { :parent => :parent },
        :conditions => ["events.date between ? and ?", first_of_year, last_of_year]
    ))
  end
  
  if RacingAssociation.current.show_only_association_sanctioned_races_on_calendar
    events.reject! do |event|
      event.sanctioned_by != RacingAssociation.current.default_sanctioned_by
    end
  end
  
  events.map!(&:root)
  
  weekly_series, events = events.partition { |event| event.is_a?(WeeklySeries) }
  competitions, events = events.partition { |event| event.is_a?(Competition) }

  [ weekly_series, events, competitions ]
end

Public Instance Methods

ancestors() click to toggle source
# File app/models/event.rb, line 477
def ancestors
  node, nodes = self, []
  nodes << node = node.parent while node.parent
  nodes
end
categories() click to toggle source

All races’ Categories and all children’s races’ Categories

# File app/models/event.rb, line 271
def categories
  _categories = races.map(&:category)
  children.inject(_categories) { |cats, child| cats + child.categories }
end
children_and_child_competitions_with_results(reload = false) click to toggle source

Returns only the children and child child_competitions with results

# File app/models/event.rb, line 246
def children_and_child_competitions_with_results(reload = false)
  children_and_child_competitions(reload).select(&:has_results_including_children?)
end
children_changed(child) click to toggle source
# File app/models/event.rb, line 303
def children_changed(child)
  # Don't trigger callbacks
  Event.update_all ["updated_at = ?", Time.zone.now], ["id = ?", id]
  true
end
children_with_results(reload = false) click to toggle source

Returns only the children with results

# File app/models/event.rb, line 241
def children_with_results(reload = false)
  children(reload).select(&:has_results_including_children?)
end
city_state() click to toggle source
# File app/models/event.rb, line 351
def city_state
  if city.present?
    if state.present?
      "#{city}, #{state}"
    else
      city
    end
  else
    if state.present?
      state
    else
      ''
    end
  end
end
default_bar_points() click to toggle source
# File app/models/event.rb, line 188
def default_bar_points
  1
end
default_discipline() click to toggle source
# File app/models/event.rb, line 192
def default_discipline
  "Road"
end
default_ironman() click to toggle source
# File app/models/event.rb, line 196
def default_ironman
  1
end
default_number_issuer() click to toggle source
# File app/models/event.rb, line 208
def default_number_issuer
  NumberIssuer.find_by_name(RacingAssociation.current.short_name)
end
default_sanctioned_by() click to toggle source
# File app/models/event.rb, line 204
def default_sanctioned_by
  RacingAssociation.current.default_sanctioned_by
end
default_state() click to toggle source
# File app/models/event.rb, line 200
def default_state
  RacingAssociation.current.state
end
descendants() click to toggle source

All children and children childrens

# File app/models/event.rb, line 484
def descendants
  _descendants = children(true)
  children.each do |child|
    _descendants = _descendants + child.descendants
  end
  _descendants
end
destroy_races() click to toggle source
# File app/models/event.rb, line 255
def destroy_races
  ActiveRecord::Base.lock_optimistically = false
  disable_notification!
  if combined_results
    combined_results.destroy_races
    combined_results.destroy
  end
  races.each do |race|
    race.results.clear
  end
  races.clear
  enable_notification!
  ActiveRecord::Base.lock_optimistically = true
end
disable_notification!() click to toggle source

Update database immediately with save!

# File app/models/event.rb, line 310
def disable_notification!
  if notification_enabled?
    ActiveRecord::Base.lock_optimistically = false
    # Don't trigger after_save callback just because we're enabling notification
    self.notification = false
    Event.update_all("notification = false", ["id = ?", id])
    children.each(&:disable_notification!)
    ActiveRecord::Base.lock_optimistically = true
  end
  false
end
discipline_id() click to toggle source
# File app/models/event.rb, line 383
def discipline_id
  Discipline[discipline].try :id
end
enable_notification!() click to toggle source

Update database immediately with save!

# File app/models/event.rb, line 323
def enable_notification!
  unless notification_enabled?
    ActiveRecord::Base.lock_optimistically = false
    # Don't trigger after_save callback just because we're enabling notification
    self.notification = true
    Event.update_all("notification = true", ["id = ?", id])
    children.each(&:enable_notification!)
    ActiveRecord::Base.lock_optimistically = true
  end
  true
end
has_results?(reload = false) click to toggle source

Will return false-positive if there are only overall series results, but those should only exist if there are “real” results. The results page should show the results in that case.

# File app/models/event.rb, line 230
def has_results?(reload = false)
  self.races(reload).any? { |r| !r.results(reload).empty? }
end
has_results_including_children?(reload = false) click to toggle source

Will return false-positive if there are only overall series results, but those should only exist if there are “real” results. The results page should show the results in that case.

# File app/models/event.rb, line 236
def has_results_including_children?(reload = false)
  races(reload).any? { |r| !r.results(reload).empty? } || children(reload).any? { |event| event.has_results?(reload) || event.has_results_including_children? }
end
inclusion_of_discipline() click to toggle source
# File app/models/event.rb, line 387
def inclusion_of_discipline
  if discipline.present? && Discipline.names.present? && !Discipline.names.include?(discipline)
    errors.add :discipline, "'#{discipline}' is not in #{Discipline.names.join(", ")}"
  end
end
inspect_debug() click to toggle source
# File app/models/event.rb, line 502
def inspect_debug
  puts("#{self.class.name.ljust(20)} #{self.date} #{self.name} #{self.discipline} #{self.id}")
  self.races(true).sort.each {|r| 
    puts("#{r.class.name.ljust(20)}   #{r.name}")
    r.results(true).sort.each {|result|
      puts("#{result.class.name.ljust(20)}      #{result.to_long_s}")
      result.scores(true).sort.each{|score|
        puts("#{score.class.name.ljust(20)}         #{score.source_result.place} #{score.source_result.race.event.name}  #{score.source_result.race.name} #{score.points}")
      }
    }
  }
  
  self.children(true).sort.each do |event|
    event.inspect_debug
  end
  
  ""
end
location() click to toggle source
# File app/models/event.rb, line 367
def location
  city_state
end
location=(value) click to toggle source

Will split on comma if city, state

# File app/models/event.rb, line 372
def location=(value)
  if value.present?
    self.city, self.state = value.split(",")
    self.city = city.strip if city
    self.state = state.strip if state
  else
    self.city = nil
    self.state = nil
  end
end
missing_children() click to toggle source

Always return empty Array

# File app/models/event.rb, line 444
def missing_children
  []
end
missing_children?() click to toggle source
# File app/models/event.rb, line 439
def missing_children?
  missing_children.any?
end
missing_parent() click to toggle source
# File app/models/event.rb, line 467
def missing_parent
  nil
end
missing_parent?() click to toggle source

Always return false

# File app/models/event.rb, line 435
def missing_parent?
  false
end
multi_day_event_children_with_no_parent() click to toggle source
# File app/models/event.rb, line 452
def multi_day_event_children_with_no_parent
  return [] unless name && date
  
  @multi_day_event_children_with_no_parent ||= SingleDayEvent.all(
    :conditions => [
      "parent_id is null and name = ? and extract(year from date) = ? 
       and ((select count(*) from events where name = ? and extract(year from date) = ? and type in ('MultiDayEvent', 'Series', 'WeeklySeries')) = 0)",
       self.name, self.date.year, self.name, self.date.year])
  # Could do this in SQL
  if @multi_day_event_children_with_no_parent.size == 1
    @multi_day_event_children_with_no_parent = []
  end
  @multi_day_event_children_with_no_parent
end
multi_day_event_children_with_no_parent?() click to toggle source
# File app/models/event.rb, line 448
def multi_day_event_children_with_no_parent?
  multi_day_event_children_with_no_parent.any?
end
notification_enabled?() click to toggle source

Child results fire change notifications? Set to false before bulk changes like event results import to prevent many pointless change notifications and CombinedTimeTrialResults recalcs Check database to ensure most recent value is used, and not a association’s out-of-date cached value

# File app/models/event.rb, line 339
def notification_enabled?
  connection.select_value("select notification from events where id = #{id}") == 1
end
parent_is_not_self() click to toggle source
# File app/models/event.rb, line 492
def parent_is_not_self
  if parent_id && parent_id == id
    errors.add("parent", "Event cannot be its own parent")
  end
end
promoter_name() click to toggle source
# File app/models/event.rb, line 393
def promoter_name
  promoter.name if promoter
end
promoter_name=(value) click to toggle source
# File app/models/event.rb, line 397
def promoter_name=(value)
  @new_promoter_name = value
end
propagate_races() click to toggle source

Synch Races with children. More accurately: create a new Race on each child Event for each Race on the parent.

# File app/models/event.rb, line 299
def propagate_races
  # Do nothing in superclass
end
races_with_results() click to toggle source

Returns only the Races with results

# File app/models/event.rb, line 251
def races_with_results
  races.select { |race| !race.results.empty? }.sort
end
root() click to toggle source
# File app/models/event.rb, line 471
def root
  node = self
  node = node.parent while node.parent
  node
end
set_defaults() click to toggle source

Defaults state to RacingAssociation.current.state, date to today, name to New Event mm-dd-yyyy NumberIssuer: RacingAssociation.current.short_name Child events use their parent’s values unless explicity overriden. And you cannot override parent values by passing in blank or nil attributes to initialize, as there is no way to differentiate missing values from nils or blanks.

# File app/models/event.rb, line 170
def set_defaults
  if new_record?
    if parent
      PROPOGATED_ATTRIBUTES.each { |attr| 
        (self[attr] = parent[attr]) if self[attr].blank? 
      }
    end
    self.bar_points = default_bar_points       if self[:bar_points].nil?
    self.date = default_date                   if self[:date].nil?
    self.discipline = default_discipline       if self[:discipline].nil?
    self.name = default_name                   if self[:name].nil?
    self.ironman = default_ironman             if self[:ironman].nil?
    self.number_issuer = default_number_issuer if number_issuer.nil?
    self.sanctioned_by = default_sanctioned_by if (parent.nil? && self[:sanctioned_by].nil?) || (parent && parent[:sanctioned_by].nil?)
    self.state = default_state                 if (parent.nil? && self[:state].nil?) || (parent && parent[:state].nil?)
  end
end
set_points_for(competition, points) click to toggle source

Set point value/factor for this Competition. Convenience method to hide CompetitionEventMembership complexity.

# File app/models/event.rb, line 344
def set_points_for(competition, points)
  # For now, allow Nil exception, but probably will want to auto-create membership in the future
  competition_event_membership = competition_event_memberships.detect { |cem| cem.competition == competition }
  competition_event_membership.points_factor = points
  competition_event_membership.save!
end
set_promoter() click to toggle source
# File app/models/event.rb, line 401
def set_promoter
  if new_promoter_name.present?
    promoters = Person.find_all_by_name_or_alias(new_promoter_name)
    case promoters.size
    when 0
      self.promoter = Person.create!(:name => new_promoter_name)
    when 1
      self.promoter = promoters.first
    else
      self.promoter = promoters.detect { |promoter| promoter.id == promoter_id } || promoters.first
    end
  elsif new_promoter_name == ""
    self.promoter = nil
  end
end
set_team() click to toggle source

Find or create associated Team based on #new_team_name

# File app/models/event.rb, line 426
def set_team
  if new_team_name.present?
    self.team = Team.find_by_name_or_alias_or_create(new_team_name)
  elsif new_team_name == ""
    self.team = nil
  end
end
team_name() click to toggle source
# File app/models/event.rb, line 417
def team_name
  team.name if team
end
team_name=(value) click to toggle source
# File app/models/event.rb, line 421
def team_name=(value)
  @new_team_name = value
end
to_s() click to toggle source
# File app/models/event.rb, line 521
def to_s
  "<#{self.class} #{id} #{discipline} #{name} #{date}>"
end
type_modifiable?() click to toggle source
# File app/models/event.rb, line 498
def type_modifiable?
  type.nil? || %w{ Event SingleDayEvent MultiDayEvent Series WeeklySeries }.include?(type)
end
update_children() click to toggle source

Update child events from parents’ attributes if child attribute has the same value as the parent before update

# File app/models/event.rb, line 278
def update_children
  return true if new_record? || children.count == 0
  changes.select { |key, value| PROPOGATED_ATTRIBUTES.include?(key) }.each do |change|
    attribute = change.first
    was = change.last.first
    if was.blank?
      SingleDayEvent.update_all(
        ["#{attribute}=?", self[attribute]], 
        ["(#{attribute}=? or #{attribute} is null or #{attribute} = '') and parent_id=?", was, 
        self[:id]]
      )
    else
      SingleDayEvent.update_all ["#{attribute}=?", self[attribute]], ["#{attribute}=? and parent_id=?", was, self[:id]]
    end
  end
  
  children.each { |child| child.update_children }
  true
end
validate_no_results() click to toggle source
# File app/models/event.rb, line 212
def validate_no_results
  races(true).each do |race|
    if race.results(true).any?
      errors.add('results', 'Cannot destroy event with results')
      return false 
    end
  end

  children(true).each do |event|
    errors.add('results', 'Cannot destroy event with children with results')
    return false unless event.validate_no_results
  end

  true
end