| Class | Race |
| In: |
app/models/race.rb
|
| Parent: | ActiveRecord::Base |
A Race is essentionally a collection of Results labelled with a Category. Races must belong to a parent Event.
Races only have some of their attributes populated. These attributes are listed in the result_columns Array.
People use say "category" where we use Race in code. Could rename this EventCategory.
Race result_columns: populated columns displayed on results page. Usually Result attributes, but also creates virtual "custom" columns.
| DEFAULT_RESULT_COLUMNS | = | %W{place number last_name first_name team_name points time}.freeze | ||
| RESULT | = | Result.new | Prototype Result used for checking valid column names |
# File app/models/export/races.rb, line 5 5: def Race.export 6: Race.export_head 7: Race.export_data 8: end
# File app/models/export/races.rb, line 34
34: def Race.export_columns
35: [
36: "id", "event_id", "category_id", "city", "state", "distance",
37: "field_size", "laps", "time", "finishers"
38: ]
39: end
# File app/models/export/races.rb, line 16
16: def Race.export_data
17: Base.export(export_data_sql, "races.csv")
18: end
# File app/models/export/races.rb, line 26
26: def Race.export_data_sql
27: "SELECT #{Race.export_columns.join(",")}
28: INTO OUTFILE '%s'
29: FIELDS TERMINATED BY ',' OPTIONALLY ENCLOSED BY '\"'
30: LINES TERMINATED BY '\\n'
31: FROM races"
32: end
# File app/models/export/races.rb, line 12
12: def Race.export_head
13: Base.export(export_head_sql, "races.txt")
14: end
# File app/models/export/races.rb, line 20
20: def Race.export_head_sql
21: "SELECT '#{Race.export_columns.join("','")}'
22: INTO OUTFILE '%s'
23: FIELDS TERMINATED BY '\\n'"
24: end
# File app/models/race.rb, line 315
315: def==(other)
316: return false unless other.is_a?(self.class)
317: unless other.new_record? or new_record?
318: return other.id == id
319: end
320: category == other.category
321: end
0..3
# File app/models/race.rb, line 34
34: def bar_points=(value)
35: if value == self.event.bar_points or value.nil?
36: self[:bar_points] = nil
37: elsif value.to_i == value.to_f
38: self[:bar_points] = value
39: else
40: raise ArgumentError, "BAR points must be an integer, but was: #{value}"
41: end
42: end
# File app/models/race.rb, line 209
209: def calculate_members_only_places!
210: event_notification_was_enabled = event.notification_enabled?
211: event.disable_notification!
212: begin
213: # count up from zero
214: last_members_only_place = 0
215: # assuming first result starting at zero+one (better than sorting results twice?)
216: last_result_place = 0
217: results.sort.each do |result|
218: place_before = result.members_only_place.to_i
219: result.members_only_place = ''
220: if result.place.to_i > 0
221: if ((result.person.nil? || (result.person && result.person.member?(result.date))) && !non_members_on_team(result))
222: # only increment if we have moved onto a new place
223: last_members_only_place += 1 if (result.place.to_i != last_members_only_place && result.place.to_i!=last_result_place)
224: result.members_only_place = last_members_only_place.to_s
225: end
226: # Slight optimization. Most of the time, no point in saving a result that hasn't changed
227: result.update_attribute('members_only_place', result.members_only_place) if place_before != result.members_only_place
228: # store to know when switching to new placement (team result feature)
229: last_result_place = result.place.to_i
230: end
231: end
232: ensure
233: event.enable_notification! if event_notification_was_enabled
234: end
235: end
# File app/models/race.rb, line 102
102: def calculate_result_columns!
103: self.result_columns = result_columns_or_default
104: result_columns.delete_if do |result_column|
105: results.all? do |result|
106: value = result.send(result_column)
107: value.blank? || value == 0 || value == 0.0
108: end
109: end
110: save!
111: end
# File app/models/race.rb, line 60
60: def category_friendly_param
61: category.try :friendly_param
62: end
# File app/models/race.rb, line 44
44: def category_name=(name)
45: if name.blank?
46: self.category = nil
47: else
48: self.category = Category.new(:name => name)
49: end
50: end
# File app/models/race.rb, line 261
261: def create_result_before(result_id)
262: results.sort!
263: if results.empty?
264: return results.create(:place => "1")
265: end
266:
267: if result_id
268: result = Result.find(result_id)
269: place = result.place
270: start_index = results.index(result)
271: for index in start_index...(results.size)
272: if results[index].place.to_i > 0
273: results[index].place = (results[index].place.to_i + 1).to_s
274: results[index].save!
275: end
276: end
277: else
278: result = results.last
279: if result.place.to_i > 0
280: place = result.place.to_i + 1
281: else
282: place = result.place
283: end
284: end
285:
286: results.create(:place => place)
287: end
# File app/models/race.rb, line 147
147: def custom_columns
148: self[:custom_columns] ||= []
149: end
Range of dates_of_birth of people in this race
# File app/models/race.rb, line 80
80: def dates_of_birth
81: raise(ArgumentError, 'Need category to calculate dates of birth') unless category
82: Date.new(date.year - category.ages.end, 1, 1)..Date.new(date.year - category.ages.begin, 12, 31)
83: end
# File app/models/race.rb, line 289
289: def destroy_result(result)
290: place = result.place
291: results.sort!
292: start_index = results.index(result) + 1
293: for index in start_index...(results.size)
294: if results[index].place.to_i > 0
295: results[index].place = results[index].place.to_i - 1
296: results[index].save!
297: end
298: end
299: result.destroy
300: end
Incorrectly doubles tandem and other team events’ field sizes
# File app/models/race.rb, line 94
94: def field_size
95: if self[:field_size].present? && self[:field_size] > 0
96: self[:field_size]
97: else
98: results.size
99: end
100: end
Ensure child team and people are not duplicates of existing records Tricky side effect — external references to new association records (category, bar_category, person, team) will not point to associated records FIXME Handle people with only a number
# File app/models/race.rb, line 155
155: def find_associated_records
156: if category && (category.new_record? || category.changed?)
157: if category.name.blank?
158: self.category = nil
159: else
160: existing_category = Category.find_by_name(category.name)
161: self.category = existing_category if existing_category
162: end
163: end
164: end
# File app/models/race.rb, line 166
166: def has_result(row_hash)
167: if row_hash["place"].present? && row_hash["place"] != "1" && row_hash["place"] != "0"
168: return true
169: end
170: if row_hash["person.first_name"].blank? &&
171: row_hash["person.last_name"].blank? &&
172: row_hash["person.road_number"].blank? &&
173: row_hash["team.name"].blank?
174: return false
175: end
176: true
177: end
# File app/models/race.rb, line 307
307: def hash
308: if new_record?
309: category.hash
310: else
311: id
312: end
313: end
# File app/models/race.rb, line 237
237: def non_members_on_team(result)
238: non_members = false
239: # if this is undeclared in environment.rb, assume this rule does not apply
240: exempt_cats = RacingAssociation.current.exempt_team_categories
241: if (exempt_cats.nil? || exempt_cats.include?(result.race.category.name))
242: return non_members
243: else
244: other_results_in_place = Result.find(:all, :conditions => ["race_id = ? and place = ?", result.race.id, result.place])
245: other_results_in_place.each { |orip|
246: unless orip.person.nil?
247: if !orip.person.member?(result.date)
248: # might as well blank out this result while we're here, saves some future work
249: result.members_only_place = ''
250: result.update_attribute 'members_only_place', result.members_only_place
251: # could also use other_results_in_place.size if needed for calculations
252: non_members = true
253: end
254: end
255: }
256: # still false if no others found, or all are members, or could not be determined (non-person)
257: non_members
258: end
259: end
Sort results by points, assign places Save! each result after place is set
# File app/models/race.rb, line 181
181: def place_results_by_points(break_ties = true, ascending = true)
182: for result in results
183: result.calculate_points
184: end
185:
186: results.sort! do |x, y|
187: x.compare_by_points(y)
188: end
189:
190: if !ascending
191: results.reverse!
192: end
193:
194: previous_result = nil
195: results.each_with_index do |result, index|
196: if index == 0
197: result.place = 1
198: else
199: if results[index - 1].compare_by_points(result, break_ties) == 0
200: result.place = results[index - 1].place
201: else
202: result.place = index + 1
203: end
204: end
205: result.save!
206: end
207: end
# File app/models/race.rb, line 113
113: def result_columns=(value)
114: if value && value.include?("name")
115: name_index = value.index("name")
116: value[name_index] = "first_name"
117: value.insert(name_index + 1, "last_name")
118: end
119:
120: if value && value.include?("place") && value.first != "place"
121: value.delete("place")
122: value.insert(0, "place")
123: end
124: self[:result_columns] = value
125: end
Default columns if empty
# File app/models/race.rb, line 128
128: def result_columns_or_default
129: self.result_columns || DEFAULT_RESULT_COLUMNS.dup
130: end
Ugh. Better here than a controller or helper, I guess.
# File app/models/race.rb, line 133
133: def result_columns_or_default_for_editing
134: columns = result_columns_or_default
135: columns.map! do |column|
136: if column == "first_name" || column == "last_name"
137: "name"
138: else
139: column
140: end
141: end
142: columns << "bar" if RacingAssociation.current.competitions.include?(:bar)
143: columns.uniq!
144: columns
145: end