class Results::ResultsFile

Import Excel results file

Result time limited to hundredths of seconds

Notes example: Senior Men Pro 1/2 | Field size: 79 riders | Laps: 2

Set DEBUG_RESULTS to Toggle expensive debug logging. E.g., DEBUG_RESULTS=yes ./script/server

Constants

COLUMN_MAP

Attributes

column_indexes[RW]
columns[RW]
custom_columns[RW]

All custom columns in file

event[RW]
race_custom_columns[RW]

Custom columns just for Race

rows[RW]
source[RW]

Public Class Methods

new(source, event) click to toggle source
# File lib/results/results_file.rb, line 73
def initialize(source, event)
  self.column_indexes = nil
  self.event = event
  self.custom_columns = Set.new
  self.race_custom_columns = Set.new
  self.source = source
end

Public Instance Methods

construct_usac_category(row) click to toggle source
# File lib/results/results_file.rb, line 229
def construct_usac_category(row)
  #category_name and gender should always be populated.
  #juniors, and conceivably masters, may be split by age group in which case the age column should
  #contain the age range. otherwise it may be empty or contain an individual racer's age.
  #The end result should look like "Junior Women 13-14" or "Junior Men"
  #category_class may or may not be populated
  #e.g. "Master B Men" or "Cat4 Female"
  if row[:age].present? && %r\d+-\d+/ =~ row[:age].to_s
    return ((row[:category_name].to_s + " " + row[:category_class].to_s + " " + row[:gender].to_s + " " + row[:age].to_s)).squeeze(" ").strip
  else
    return ((row[:category_name].to_s + " " + row[:category_class].to_s + " " + row[:gender].to_s)).squeeze(" ").strip
  end
end
create_columns(spreadsheet_row) click to toggle source

Create Hash of normalized column name indexes Convert column names to lowercase and underscore. Use COLUMN_MAP to normalize.

Example: Place, Num, First Name { :place => 0, :number => 1, :first_name => 2 }

# File lib/results/results_file.rb, line 143
def create_columns(spreadsheet_row)
  self.column_indexes = Hash.new
  self.columns = []
  spreadsheet_row.each_with_index do |cell, index|
    cell_string = cell.to_s
    if cell_string.present?
      cell_string.strip!
      cell_string.gsub!(%r^"/, '')
      cell_string.gsub!(%r"$/, '')
    end

    Rails.logger.debug("CELLS: #{cell_string}, #{spreadsheet_row[index - 1].blank?}, #{spreadsheet_row[index + 1].blank?}") if debug?
    if cell_string == "Name" && spreadsheet_row[index + 1].blank?
      cell_string = "First Name"
    elsif cell_string.blank? && "Name" == spreadsheet_row[index - 1]
      cell_string = "Last Name"
    end

    if index == 0 && cell_string.blank?
      column_indexes[:place] = 0
    elsif cell_string.present?
      cell_string = cell_string.downcase.underscore
      cell_string.gsub!(" ", "_")
      cell_string = COLUMN_MAP[cell_string] if COLUMN_MAP[cell_string]
      
      if cell_string.present?
        if usac_results_format?
          if prototype_result.respond_to?(cell_string.to_sym)
            column_indexes[cell_string.to_sym] = index
            self.columns << cell_string 
          end
        else
          column_indexes[cell_string.to_sym] = index
          self.columns << cell_string 
          if !prototype_result.respond_to?(cell_string.to_sym)
            self.custom_columns << cell_string
            self.race_custom_columns << cell_string
          end
        end
      end
    end
  end

  Rails.logger.debug("Results::ResultsFile #{Time.zone.now} Create column indexes #{self.column_indexes.inspect}") if debug?
  self.column_indexes
end
create_result(row, race) click to toggle source
# File lib/results/results_file.rb, line 253
def create_result(row, race)
  if race
    result = race.results.build(result_attributes(row, race))
    result.updated_by = @event.name

    if row.same_time?
      result.time = row.previous.result.time
    end

    if result.place.to_i > 0
      result.place = result.place.to_i
    elsif result.place.present?
      result.place = result.place.upcase rescue result.place
    elsif row.previous[:place].present? && row.previous[:place].to_i == 0
      result.place = row.previous[:place]
    end

    # USAC format input may contain an age range in the age column for juniors.
    if row[:age].present? && %r\d+-\d+/ =~ row[:age].to_s
      result.age = nil
      result.age_group = row[:age]
    end

    result.cleanup
    result.save!
    row.result = result
    Rails.logger.debug("Results::ResultsFile #{Time.zone.now} create result #{race} #{result.place}") if debug?
  else
    # TODO Maybe a hard exception or error would be better?
    Rails.logger.warn("No race. Skip.")
  end
end
create_rows(worksheet) click to toggle source
# File lib/results/results_file.rb, line 111
def create_rows(worksheet)
  # Need all rows. Decorate them before inspecting them.
  # Drop empty ones
  self.rows = []
  previous_row = nil
  worksheet.each do |spreadsheet_row|
    if debug?
      Rails.logger.debug("Results::ResultsFile #{Time.zone.now} row #{spreadsheet_row.to_a.join(', ')}")
      spreadsheet_row.each_with_index do |cell, index|
        Rails.logger.debug("number_format pattern to_s to_f #{spreadsheet_row.format(index).number_format}  #{spreadsheet_row.format(index).pattern} #{cell.to_s} #{cell.to_f if cell.respond_to?(:to_f)} #{cell.class}")
      end
    end
    row = Results::Row.new(spreadsheet_row, column_indexes, usac_results_format?)
    unless row.blank?
      if column_indexes.nil?
        create_columns(spreadsheet_row)
      else
        row.previous = previous_row
        previous_row.next = row if previous_row
        rows << row
        previous_row = row
      end
    end
  end
end
debug?() click to toggle source
# File lib/results/results_file.rb, line 315
def debug?
  ENV["DEBUG_RESULTS"].present? && Rails.logger.debug?
end
find_or_create_race(row) click to toggle source
# File lib/results/results_file.rb, line 209
def find_or_create_race(row)
  if usac_results_format?
    category = Category.find_or_create_by_name(construct_usac_category(row))
  else
    category = Category.find_or_create_by_name(row.first)
  end
  race = event.races.detect { |race| race.category == category }
  if race
    race.results.clear
  else
    race = event.races.build(:category => category, :notes => row.notes)
  end
  race.result_columns = columns
  race.custom_columns = race_custom_columns.to_a
  race_custom_columns = Set.new
  race.save!
  Rails.logger.info("Results::ResultsFile #{Time.zone.now} create race #{category}")
  race
end
import() click to toggle source

See trac.butlerpress.com/racing_on_rails/wiki/SampleImportFiles for format details and examples.

# File lib/results/results_file.rb, line 82
def import
  Rails.logger.info("Results::ResultsFile #{Time.zone.now} import")

  Event.transaction do
    event.disable_notification!
    book = ::Spreadsheet.open(source.path)
    book.worksheets.each do |worksheet|
      race = nil
      create_rows(worksheet)

      rows.each do |row|
        Rails.logger.debug("Results::ResultsFile #{Time.zone.now} row #{row.spreadsheet_row.to_a.join(', ')}") if debug?
        if race?(row)
          race = find_or_create_race(row)
          # This row is also a result. I.e., no separate race header row.
          if usac_results_format?
            create_result(row, race)
          end
        elsif result?(row)
          create_result(row, race)
        end
      end
    end
    event.enable_notification!
    CombinedTimeTrialResults.create_or_destroy_for!(event)
  end
  Rails.logger.info("Results::ResultsFile #{Time.zone.now} import done")
end
prototype_result() click to toggle source
# File lib/results/results_file.rb, line 307
def prototype_result
  @prototype_result ||= Result.new.freeze
end
race?(row) click to toggle source
# File lib/results/results_file.rb, line 190
def race?(row)
  if usac_results_format?
    #new race when one of the key fields changes: category, gender, class or age if age is a range
    #i was looking for place = 1 but it is possible that all in race were dq or dnf or dns
    return false if column_indexes.nil? || row.blank?
    return true if row.previous.blank?
    #break if age is a range and it has changed
    if row[:age].present? && %r\d+-\d+/ =~ row[:age].to_s
      return true unless row[:age] == row.previous[:age]
    end
    return false if row[:category_name] == row.previous[:category_name] && row[:gender] == row.previous[:gender] && row[:category_class] == row.previous[:category_class]
    return true
  else  
    return false if column_indexes.nil? || row.last? || row.blank? || (row.next && row.next.blank?)
    return false if row.place && row.place.to_i != 0
    row.next && row.next.place && row.next.place.to_i == 1
  end
end
result?(row) click to toggle source
# File lib/results/results_file.rb, line 243
def result?(row)
  return false unless column_indexes
  return false if row.blank?
  return true if row.place.present? || row[:number].present? || row[:license].present? || row[:team_name].present?
  if !(row[:first_name].blank? && row[:last_name].blank? && row[:name].blank?)
    return true
  end
  false
end
result_attributes(row, race) click to toggle source
# File lib/results/results_file.rb, line 286
def result_attributes(row, race)
  attributes = row.to_hash.dup
  custom_attributes = {}
  attributes.delete_if do |key, value|
    _key = key.to_s.to_sym
    if race.custom_columns.include?(_key)
      custom_attributes[_key] = case value
      when Time
        value.strftime "%H:%M:%S"
      else
        value
      end
      true
    else
      false
    end
  end
  attributes.merge! :custom_attributes => custom_attributes
  attributes
end
usac_results_format?() click to toggle source
# File lib/results/results_file.rb, line 311
def usac_results_format?
  RacingAssociation.current.usac_results_format?
end