Class Event
In: app/models/event.rb
Parent: ActiveRecord::Base

Superclass for anything that can have results:

  • Event (Superclass only used to organizes multiple sets of results on a single day.
           Examples: Alpenrose Challenge Morning Session, Alpenrose Challenge Afternoon Session)
    
  • SingleDayEvent (Most events, an event on a particular date. Appears on calendar.)
  • MultiDayEvent (Spans more than one day. has many SingleDayEvent children)
  • Competition (Results are calculated/derived from other events’ results. Examples: Combined TT times, OBRA BAR)

instructional: class or clinc practice: training session

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

  • parent-children: A one-to-many tree. Example: stage race parent + stages children. Used for calculating competition results and schedule display. Does not include Competitions, even though Competitions can have Event parents.

    A purer children association would return all child Events and Competitions

 (that's how they are in the database). But we almost always want just the child Events,
  and not the Competitions.
  • parent-child_competitions: A one-to-many tree. Example: stage race MultiDayEvent parent with GC and KOM child_competitions. Typically combined with children when we really do want all child Competitions and Events.
  • competition_event_memberships: many-to-many from Events to Competitions. Example: Banana Belt I can be a member of the Oregon Cup and the OBRA Road BAR. And the Oregon Cup also includes Kings Valley RR, Table Rock RR, etc. CompetitionEventMembership is a meaningul class in its own right.

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

Methods

Included Modules

Comparable Export::Events

Constants

PROPOGATED_ATTRIBUTES = %w{ city discipline flyer name number_issuer_id promoter_id prize_list sanctioned_by state time velodrome_id time postponed cancelled flyer_approved instructional practice sanctioned_by email phone team_id beginner_friendly } unless defined?(PROPOGATED_ATTRIBUTES)

Attributes

new_promoter_name  [R] 
new_team_name  [R] 

Public Class methods

[Source]

   # File app/models/export/events.rb, line 5
5:     def Event.export
6:       Event.export_head
7:       Event.export_data
8:     end

[Source]

    # File app/models/export/events.rb, line 34
34:     def Event.export_columns
35:       [
36:         "id", "parent_id", "name", "discipline", "date", "time",
37:         "sanctioned_by", "type", "city", "state", "instructional", "practice",
38:         "postponed", "beginner_friendly", "cancelled"
39:       ]
40:     end

[Source]

    # File app/models/export/events.rb, line 16
16:     def Event.export_data
17:       Base.export(export_data_sql, "events.csv")
18:     end

[Source]

    # File app/models/export/events.rb, line 26
26:     def Event.export_data_sql
27:       "SELECT #{Event.export_columns.join(",")}
28:        INTO OUTFILE '%s'
29:        FIELDS TERMINATED BY ',' OPTIONALLY ENCLOSED BY '\"'
30:        LINES TERMINATED BY '\\n'
31:        FROM events"
32:     end

[Source]

    # File app/models/export/events.rb, line 12
12:     def Event.export_head
13:       Base.export(export_head_sql, "events.txt")
14:     end

[Source]

    # File app/models/export/events.rb, line 20
20:     def Event.export_head_sql
21:       "SELECT '#{Event.export_columns.join("','")}'
22:        INTO OUTFILE '%s'
23:        FIELDS TERMINATED BY '\\n'"
24:     end

[Source]

     # File app/models/event.rb, line 158
158:   def Event.find_all_bar_for_discipline(discipline, year = Date.today.year)
159:     first_of_year = Date.new(year, 1, 1)
160:     last_of_year = Date.new(year + 1, 1, 1) - 1
161:       discipline_names = [discipline]
162:       discipline_names << 'Circuit' if discipline.downcase == 'road'
163:       Set.new(Event.find(
164:           :all,
165:           :select => "distinct events.id, events.*",
166:           :conditions => [%Q{
167:               events.date between ? and ?
168:               and events.discipline in (?)
169:               and bar_points > 0
170:               }, first_of_year, last_of_year, discipline_names]
171:       ))
172: 
173:   end

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

[Source]

     # File app/models/event.rb, line 113
113:   def Event.find_all_with_results(year = Date.today.year, discipline = nil)
114:     # Maybe this should be its own class, since it has knowledge of Event and Result?
115:     first_of_year = Date.new(year, 1, 1)
116:     last_of_year = Date.new(year + 1, 1, 1) - 1
117:     
118:     if discipline
119:       discipline_names = [discipline.name]
120:       if discipline == Discipline['road']
121:         discipline_names << 'Circuit'
122:       end
123:       events = Set.new(Event.find(
124:           :all,
125:           :select => "distinct events.id, events.*",
126:           :joins => { :races => :results },
127:           :include => { :parent => :parent },
128:           :conditions => [%Q{
129:               events.date between ? and ? 
130:               and events.discipline in (?)
131:               }, first_of_year, last_of_year, discipline_names]
132:       ))
133:       
134:     else
135:       events = Set.new(Event.find(
136:           :all,
137:           :select => "distinct events.id, events.*",
138:           :joins => { :races => :results },
139:           :include => { :parent => :parent },
140:           :conditions => ["events.date between ? and ?", first_of_year, last_of_year]
141:       ))
142:     end
143:     
144:     if RacingAssociation.current.show_only_association_sanctioned_races_on_calendar
145:       events.reject! do |event|
146:         event.sanctioned_by != RacingAssociation.current.default_sanctioned_by
147:       end
148:     end
149:     
150:     events.map!(&:root)
151:     
152:     weekly_series, events = events.partition { |event| event.is_a?(WeeklySeries) }
153:     competitions, events = events.partition { |event| event.is_a?(Competition) }
154: 
155:     [ weekly_series, events, competitions ]
156:   end

Return list of every year that has at least one event

[Source]

     # File app/models/event.rb, line 97
 97:   def Event.find_all_years
 98:     years = [ RacingAssociation.current.effective_year ] +
 99:     connection.select_values(
100:       "select distinct extract(year from date) from events"
101:     ).map(&:to_i)
102:     years = years.uniq.sort
103:     
104:     if years.size == 1
105:       years
106:     else
107:       ((years.first)..(years.last)).to_a.reverse
108:     end
109:   end

[Source]

     # File app/models/event.rb, line 175
175:   def Event.friendly_class_name
176:     name.underscore.humanize.titleize
177:   end

Public Instance methods

[Source]

     # File app/models/event.rb, line 617
617:   def <=>(other)
618:     return -1 if other.nil?
619: 
620:     if date 
621:       if other.date
622:         return date <=> other.date
623:       else
624:         return -1
625:       end
626:     elsif other.date
627:       return 1
628:     end 
629:     
630:     unless new_record? || other.new_record?
631:       return id <=> other.id
632:     end
633:     
634:     0
635:   end

[Source]

     # File app/models/event.rb, line 593
593:   def ==(other)
594:     if self.equal?(other)
595:       return true
596:     end
597:     
598:     if other.nil? || new_record? || other.new_record?
599:       return false
600:     end
601: 
602:     id == other.id
603:   end

[Source]

     # File app/models/event.rb, line 179
179:   def after_initialize
180:     set_defaults
181:   end

[Source]

     # File app/models/event.rb, line 577
577:   def ancestors
578:     node, nodes = self, []
579:     nodes << node = node.parent while node.parent
580:     nodes
581:   end

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

[Source]

     # File app/models/event.rb, line 299
299:   def categories
300:     _categories = races.map(&:category)
301:     children.inject(_categories) { |cats, child| cats + child.categories }
302:   end

Returns only the children and child child_competitions with results

[Source]

     # File app/models/event.rb, line 276
276:   def children_and_child_competitions_with_results(reload = false)
277:     children_and_child_competitions(reload).select(&:has_results_including_children?)
278:   end

[Source]

     # File app/models/event.rb, line 331
331:   def children_changed(child)
332:     # Don't trigger callbacks
333:     Event.update_all ["updated_at = ?", Time.zone.now], ["id = ?", id]
334:     true
335:   end

Returns only the children with results

[Source]

     # File app/models/event.rb, line 271
271:   def children_with_results(reload = false)
272:     children(reload).select(&:has_results_including_children?)
273:   end

[Source]

     # File app/models/event.rb, line 417
417:   def city_state
418:     if city.present?
419:       if state.present?
420:         "#{city}, #{state}"
421:       else
422:         city
423:       end
424:     else
425:       if state.present?
426:         state
427:       else
428:         ''
429:       end
430:     end
431:   end

[Source]

     # File app/models/event.rb, line 502
502:   def date_range_long_s
503:     date.to_s :long_with_week_day
504:   end

[Source]

     # File app/models/event.rb, line 494
494:   def date_range_s(format = :short)
495:     if format == :long
496:       date.strftime('%m/%d/%Y')
497:     else
498:       "#{date.month}/#{date.day}"
499:     end
500:   end

[Source]

     # File app/models/event.rb, line 206
206:   def default_bar_points
207:     1
208:   end

[Source]

     # File app/models/event.rb, line 210
210:   def default_date
211:     if parent.present?
212:       parent.date
213:     else
214:       Date.today
215:     end
216:   end

[Source]

     # File app/models/event.rb, line 218
218:   def default_discipline
219:     "Road"
220:   end

[Source]

     # File app/models/event.rb, line 222
222:   def default_ironman
223:     1
224:   end

[Source]

     # File app/models/event.rb, line 226
226:   def default_name
227:     "New Event #{self.date.strftime("%m-%d-%Y")}"
228:   end

[Source]

     # File app/models/event.rb, line 238
238:   def default_number_issuer
239:     NumberIssuer.find_by_name(RacingAssociation.current.short_name)
240:   end

[Source]

     # File app/models/event.rb, line 234
234:   def default_sanctioned_by
235:     RacingAssociation.current.default_sanctioned_by
236:   end

[Source]

     # File app/models/event.rb, line 230
230:   def default_state
231:     RacingAssociation.current.state
232:   end

[Source]

     # File app/models/event.rb, line 285
285:   def destroy_races
286:     disable_notification!
287:     if combined_results
288:       combined_results.destroy_races
289:       combined_results.destroy
290:     end
291:     races.each do |race|
292:       race.results.clear
293:     end
294:     races.clear
295:     enable_notification!
296:   end

Update database immediately with save!

[Source]

     # File app/models/event.rb, line 338
338:   def disable_notification!
339:     if notification_enabled?
340:       ActiveRecord::Base.lock_optimistically = false
341:       # Don't trigger after_save callback just because we're enabling notification
342:       self.notification = false
343:       Event.update_all("notification = false", ["id = ?", id])
344:       children.each(&:disable_notification!)
345:       ActiveRecord::Base.lock_optimistically = true
346:     end
347:     false
348:   end

[Source]

     # File app/models/event.rb, line 449
449:   def discipline_id
450:     Discipline[discipline].id if Discipline[discipline]
451:   end

Update database immediately with save!

[Source]

     # File app/models/event.rb, line 351
351:   def enable_notification!
352:     unless notification_enabled?
353:       ActiveRecord::Base.lock_optimistically = false
354:       # Don't trigger after_save callback just because we're enabling notification
355:       self.notification = true
356:       Event.update_all("notification = true", ["id = ?", id])
357:       children.each(&:enable_notification!)
358:       ActiveRecord::Base.lock_optimistically = true
359:     end
360:     true
361:   end

[Source]

     # File app/models/event.rb, line 396
396:   def end_date
397:     if children.any?
398:       children.sort.last.date
399:     else
400:       start_date
401:     end
402:   end

[Source]

     # File app/models/event.rb, line 605
605:   def eql?(other)
606:     if self.equal?(other)
607:       return true
608:     end
609: 
610:     if other.nil? || new_record? || other.new_record?
611:       return false
612:     end
613: 
614:     id == other.id
615:   end

[Source]

     # File app/models/event.rb, line 589
589:   def friendly_class_name
590:     self.class.friendly_class_name
591:   end

Try to intelligently combined parent name and child name for schedule pages

[Source]

     # File app/models/event.rb, line 520
520:   def full_name
521:     if parent.nil?
522:       name
523:     elsif parent.full_name == name
524:       name
525:     elsif name[ parent.full_name ]
526:       name
527:     else
528:       "#{parent.full_name}: #{name}"
529:     end
530:   end

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.

[Source]

     # File app/models/event.rb, line 260
260:   def has_results?(reload = false)
261:     self.races(reload).any? { |r| !r.results(reload).empty? }
262:   end

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.

[Source]

     # File app/models/event.rb, line 266
266:   def has_results_including_children?(reload = false)
267:     races(reload).any? { |r| !r.results(reload).empty? } || children(reload).any? { |event| event.has_results?(reload) || event.has_results_including_children? }
268:   end

[Source]

     # File app/models/event.rb, line 637
637:   def inspect_debug
638:     puts("#{self.class.name.ljust(20)} #{self.date} #{self.name} #{self.discipline} #{self.id}")
639:     self.races(true).each {|r| 
640:       puts("#{r.class.name.ljust(20)}   #{r.name}")
641:       r.results(true).sort.each {|result|
642:         puts("#{result.class.name.ljust(20)}      #{result.to_long_s}")
643:         result.scores(true).each{|score|
644:           puts("#{score.class.name.ljust(20)}         #{score.source_result.place} #{score.source_result.race.event.name}  #{score.source_result.race.name} #{score.points}")
645:         }
646:       }
647:     }
648:     
649:     self.children(true).each do |event|
650:       event.inspect_debug
651:     end
652:     
653:     ""
654:   end

[Source]

     # File app/models/event.rb, line 433
433:   def location
434:     city_state
435:   end

Will split on comma if city, state

[Source]

     # File app/models/event.rb, line 438
438:   def location=(value)
439:     if value.present?
440:       self.city, self.state = value.split(",")
441:       self.city = city.strip if city
442:       self.state = state.strip if state
443:     else
444:       self.city = nil
445:       self.state = nil
446:     end
447:   end

Always return empty Array

[Source]

     # File app/models/event.rb, line 543
543:   def missing_children
544:     []
545:   end

[Source]

     # File app/models/event.rb, line 538
538:   def missing_children?
539:     missing_children.any?
540:   end

[Source]

     # File app/models/event.rb, line 567
567:   def missing_parent
568:     nil
569:   end

Always return false

[Source]

     # File app/models/event.rb, line 534
534:   def missing_parent?
535:     false
536:   end

[Source]

     # File app/models/event.rb, line 551
551:   def multi_day_event_children_with_no_parent
552:     return [] unless name && date
553:     
554:     @multi_day_event_children_with_no_parent ||= SingleDayEvent.find(
555:       :all, 
556:       :conditions => [
557:         "parent_id is null and name = ? and extract(year from date) = ? 
558:          and ((select count(*) from events where name = ? and extract(year from date) = ? and type in ('MultiDayEvent', 'Series', 'WeeklySeries')) = 0)",
559:          self.name, self.date.year, self.name, self.date.year])
560:     # Could do this in SQL
561:     if @multi_day_event_children_with_no_parent.size == 1
562:       @multi_day_event_children_with_no_parent = []
563:     end
564:     @multi_day_event_children_with_no_parent
565:   end

[Source]

     # File app/models/event.rb, line 547
547:   def multi_day_event_children_with_no_parent?
548:     multi_day_event_children_with_no_parent.any?
549:   end

[Source]

     # File app/models/event.rb, line 409
409:   def multiple_days?
410:     end_date > start_date
411:   end

[Source]

     # File app/models/event.rb, line 515
515:   def name_with_date
516:     "#{name} (#{short_date})"
517:   end

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

[Source]

     # File app/models/event.rb, line 367
367:   def notification_enabled?
368:     connection.select_value("select notification from events where id = #{id}") == 1
369:   end

[Source]

     # File app/models/event.rb, line 583
583:   def parent_is_not_self
584:     if parent_id && parent_id == id
585:       errors.add("parent", "Event cannot be its own parent")
586:     end
587:   end

Parent‘s name. Own name if no parent

[Source]

     # File app/models/event.rb, line 507
507:   def parent_name
508:     if parent.nil?
509:       name
510:     else
511:       parent.name
512:     end
513:   end

[Source]

     # File app/models/event.rb, line 453
453:   def promoter_name
454:     promoter.name if promoter
455:   end

[Source]

     # File app/models/event.rb, line 457
457:   def promoter_name=(value)
458:     @new_promoter_name = value
459:   end

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

[Source]

     # File app/models/event.rb, line 327
327:   def propagate_races
328:     # Do nothing in superclass
329:   end

Returns only the Races with results

[Source]

     # File app/models/event.rb, line 281
281:   def races_with_results
282:     races.select { |race| !race.results.empty? }.sort
283:   end

[Source]

     # File app/models/event.rb, line 571
571:   def root
572:     node = self
573:     node = node.parent while node.parent
574:     node
575:   end

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 override. 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.

[Source]

     # File app/models/event.rb, line 188
188:   def set_defaults
189:     if new_record?
190:       if parent
191:         PROPOGATED_ATTRIBUTES.each { |attr| 
192:           (self[attr] = parent[attr]) if self[attr].blank? 
193:         }
194:       end
195:       self.bar_points = default_bar_points       if self[:bar_points].nil?
196:       self.date = default_date                   if self[:date].nil?
197:       self.discipline = default_discipline       if self[:discipline].nil?
198:       self.name = default_name                   if self[:name].nil?
199:       self.ironman = default_ironman             if self[:ironman].nil?
200:       self.number_issuer = default_number_issuer if number_issuer.nil?
201:       self.sanctioned_by = default_sanctioned_by if (parent.nil? && self[:sanctioned_by].nil?) || (parent && parent[:sanctioned_by].nil?)
202:       self.state = default_state                 if (parent.nil? && self[:state].nil?) || (parent && parent[:state].nil?)
203:     end
204:   end

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

[Source]

     # File app/models/event.rb, line 372
372:   def set_points_for(competition, points)
373:     # For now, allow Nil exception, but probably will want to auto-create membership in the future
374:     competition_event_membership = competition_event_memberships.detect { |cem| cem.competition == competition }
375:     competition_event_membership.points_factor = points
376:     competition_event_membership.save!
377:   end

[Source]

     # File app/models/event.rb, line 461
461:   def set_promoter
462:     if new_promoter_name.present?
463:       promoters = Person.find_all_by_name_or_alias(new_promoter_name)
464:       case promoters.size
465:       when 0
466:         self.promoter = Person.create!(:name => new_promoter_name)
467:       when 1
468:         self.promoter = promoters.first
469:       else
470:         self.promoter = promoters.detect { |promoter| promoter.id == promoter_id } || promoters.first
471:       end
472:     elsif new_promoter_name == ""
473:       self.promoter = nil
474:     end
475:   end

Find or create associated Team based on new_team_name

[Source]

     # File app/models/event.rb, line 486
486:   def set_team
487:     if new_team_name.present?
488:       self.team = Team.find_by_name_or_alias_or_create(new_team_name)
489:     elsif new_team_name == ""
490:       self.team = nil
491:     end
492:   end

Format for schedule page primarily

[Source]

     # File app/models/event.rb, line 380
380:   def short_date
381:     return '' unless date
382:     prefix = ' ' if date.month < 10
383:     suffix = ' ' if date.day < 10
384:     "#{prefix}#{date.month}/#{date.day}#{suffix}"
385:   end

date

[Source]

     # File app/models/event.rb, line 388
388:   def start_date
389:     date
390:   end

[Source]

     # File app/models/event.rb, line 392
392:   def start_date=(date)
393:     self.date = date
394:   end

[Source]

     # File app/models/event.rb, line 477
477:   def team_name
478:     team.name if team
479:   end

[Source]

     # File app/models/event.rb, line 481
481:   def team_name=(value)
482:     @new_team_name = value
483:   end

[Source]

     # File app/models/event.rb, line 656
656:   def to_s
657:     "<#{self.class} #{id} #{discipline} #{name} #{date}>"
658:   end

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

[Source]

     # File app/models/event.rb, line 306
306:   def update_children
307:     return true if new_record? || children.count == 0
308:     changes.select { |key, value| PROPOGATED_ATTRIBUTES.include?(key) }.each do |change|
309:       attribute = change.first
310:       was = change.last.first
311:       if was.blank?
312:         SingleDayEvent.update_all(
313:           ["#{attribute}=?", self[attribute]], 
314:           ["(#{attribute}=? or #{attribute} is null or #{attribute} = '') and parent_id=?", was, 
315:           self[:id]]
316:         )
317:       else
318:         SingleDayEvent.update_all ["#{attribute}=?", self[attribute]], ["#{attribute}=? and parent_id=?", was, self[:id]]
319:       end
320:     end
321:     
322:     children.each { |child| child.update_children }
323:     true
324:   end

Does nothing. Allows us to treat Events and MultiDayEvents the same.

[Source]

     # File app/models/event.rb, line 414
414:   def update_date
415:   end

[Source]

     # File app/models/event.rb, line 242
242:   def validate_no_results
243:     races(true).each do |race|
244:       if race.results(true).any?
245:         errors.add('results', 'Cannot destroy event with results')
246:         return false 
247:       end
248:     end
249: 
250:     children(true).each do |event|
251:       errors.add('results', 'Cannot destroy event with children with results')
252:       return false unless event.validate_no_results
253:     end
254: 
255:     true
256:   end

[Source]

     # File app/models/event.rb, line 404
404:   def year
405:     return nil unless date
406:     date.year
407:   end

[Validate]