haxney/railskating

View on GitHub
lib/results_scrapers/importer.rb

Summary

Maintainability
B
4 hrs
Test Coverage
module ResultsScrapers::Importer
  # Imports an event hash into the database.
  #
  # @param [Hash] hash A hash returned from a `scrape_event` function.
  # @param [Competition] comp The existing {Competition} model to associate this
  #   event with. All {Adjudicator}s must already exist.
  #
  # @return [Event] A newly-created {Event} filled in with information from
  #   `hash`.
  def self.import_event(hash, comp)
    comp.transaction do
      level = hash[:level].parameterize.underscore.upcase.to_sym
      event = comp.events.create(level: Constants::Levels.const_get(level),
                                 number: hash[:number])
      se_hash = {}
      hash[:dances].each_index do |i|
        dance = hash[:dances][i]
        dance_model = Dance.find_by!(name: hash[:section] + ' ' + dance)
        sub_event = event.sub_events.create(dance: dance_model,
                                            weight: i)
        se_hash[dance] = sub_event
      end

      judge_hash = {}
      comp.adjudicators.each do |a|
        judge_hash[a.shorthand] = a
      end

      hash[:rounds].each { |r| import_round(r, event, se_hash, judge_hash) }
      event.save!

      Rails.logger.tagged('scraping', 'import') {
        Rails.logger.info("Imported event #{event.number} for competition #{comp.id}")
      }

      event
    end
  end

  # Imports a single round into the database.
  #
  # @param [Hash] hash A hash returned from a `scrape_*_round` function.
  # @param [Event] event Event to which to add the round.
  # @param [{String => SubEvent}] se_hash A hash from dance names to {SubEvent}
  #   objects.
  # @param [{String => Adjudicator}] judge_hash A hash from judge shorthands to
  #   {Adjudicator} models.
  #
  # @return [Round] A newly-created {Round} imported from the hash.
  def self.import_round(hash, event, se_hash, judge_hash)
    round = event.rounds.create(number: hash[:number],
                                final: hash[:final],
                                adjudicators: hash[:judges].map { |j| judge_hash[j] },
                                # Just assume that exactly the number recalled
                                # were requested, since the real info is not
                                # present on the site.
                                requested: hash[:couples]
                                  .select { |c| c[:recall_or_place] }.length)
    sr_hash = Hash[se_hash.map do |dance, se|
      [dance, round.sub_rounds.create(sub_event: se)]
    end]

    hash[:couples].each do |couple_hash|
      no_name = couple_hash[:no_name]

      lead = if no_name
               Constants::Users::UNKNOWN_LEAD
             else
               lead_team = Team.find_or_create_by(name: couple_hash[:lead_team])
               # Assume that only first name and last name are present. If more
               # than two words, then the first word is the first name and all
               # remaining words are the last name.
               lead_names = couple_hash[:lead_name].split($;, 2)
               User.find_or_create_by(first_name: lead_names.first,
                                    last_name: lead_names.last,
                                    team: lead_team)
             end


      follow = if no_name
                 Constants::Users::UNKNOWN_FOLLOW
               else
                 follow_team = Team.find_or_create_by(name: couple_hash[:follow_team])
                 follow_names = couple_hash[:follow_name].split($;, 2)
                 User.find_or_create_by(first_name: follow_names.first,
                                        last_name: follow_names.last,
                                        team: follow_team)
               end
      couple = Couple.find_or_create_by(lead: lead,
                                        follow: follow,
                                        event: event,
                                        number: couple_hash[:number])
      round.couples << couple # Hopefully Rails takes care not to double-insert couples

      couple_hash[:dances].each do |d, marks|
        sub_round = sr_hash[d]
        marks.each do |judge, placement|
          # `mark` might be `false`, in which case a {Mark} object should not be
          # created.
          if placement
            placement = 0 if placement == true
            Mark.create(adjudicator: judge_hash[judge],
                        couple: couple,
                        sub_round: sub_round,
                        placement: placement)
          end
        end
      end
    end
  end

  # Imports a competition hash into the database.
  #
  # @param [Hash] hash A hash returned from a `scrape_comp` function.
  #
  # @return [Event] A newly-created {Competition} filled in with information
  #   from `hash`.
  def self.import_comp(hash)
    Competition.transaction do
      Rails.logger.tagged('scraping', 'import') {
        Rails.logger.info("Beginning import for competition '#{hash[:name]} #{hash[:year]}'")
      }

      comp = Competition.create(name: hash[:name])
      hash[:judges].each do |j|
        first_name = j[:name].split.first
        last_name = j[:name].split[1..-1].join(' ')
        u = User.find_or_create_by(first_name: first_name,
                                   last_name: last_name)

        ad = comp.adjudicators.create(shorthand: j[:shorthand],
                                      user: u)
      end

      hash[:events].each do |event|
        # Skip events which have not been parsed.
        next unless event[:dances]
        import_event(event, comp)
      end

      n, y = [hash[:name], hash[:year]]

      Rails.logger.tagged('scraping', 'import') {
        Rails.logger.info("Completed import for competition '#{n} #{y}'")
      }

      comp
    end
  end
end