vol1ura/Sat_9am_5km

View on GitHub
app/services/athletes/reuniter.rb

Summary

Maintainability
A
0 mins
Test Coverage
A
96%
# frozen_string_literal: true

module Athletes
  class Reuniter < ApplicationService
    SKIPPED_ATTRIBUTES = %w[id created_at updated_at name user_id stats].freeze
    MODIFIED_ATTRIBUTES = %w[parkrun_code fiveverst_code parkzhrun_code runpark_code club_id event_id male].freeze
    private_constant :SKIPPED_ATTRIBUTES, :MODIFIED_ATTRIBUTES

    def initialize(collection, ids)
      @collection = collection
      @ids = ids
    end

    def call
      return false if @collection.where.not(user_id: nil).size > 1
      return false unless athlete

      grab_modified_attributes_from_collection!
      update_results_seconds
      replace_all_by_one!
      StatsUpdate.call(athlete)
      schedule_telegram_notification
      ClearCache.call
      true
    rescue StandardError => e
      Rollbar.error e, ids: @ids.inspect
      false
    end

    private

    def athlete
      @athlete ||= @collection.where.not(user_id: nil).take || @collection.where.not(name: nil).take
    end

    def grab_modified_attributes_from_collection!
      MODIFIED_ATTRIBUTES.each do |attr|
        athlete.public_send(:"#{attr}=", @collection.where.not(attr => nil).take&.send(attr)) unless athlete.send(attr)
        unmodified_attributes.delete(attr)
      end

      return if unmodified_attributes.empty?

      raise "Athletes reuniter skips modification of public attribute(s): #{unmodified_attributes.join(', ')}"
    end

    def unmodified_attributes
      @unmodified_attributes ||= athlete.attribute_names - SKIPPED_ATTRIBUTES
    end

    def update_results_seconds
      athlete_seconds = @collection.pluck(:stats).sum([]) { |s| s.dig('results', 'seconds') || [] }.uniq.sort
      athlete.stats.deep_merge!('results' => { 'seconds' => athlete_seconds })
    end

    def replace_all_by_one!
      ActiveRecord::Base.transaction do
        # rubocop:disable Rails/SkipsModelValidations
        Result.where(athlete_id: @ids).update_all(athlete_id: athlete.id)
        Volunteer.where(athlete_id: @ids).update_all(athlete_id: athlete.id)
        update_all_trophies!
        @collection.where.not(id: athlete.id).destroy_all
        athlete.save!
        # rubocop:enable Rails/SkipsModelValidations
      end
    end

    def update_all_trophies!
      Trophy.where(athlete_id: @ids).find_each do |trophy|
        if (athlete_trophy = athlete.trophies.find_by(badge_id: trophy.badge_id))
          athlete_trophy.update!(date: trophy.date) if trophy.date && trophy.date > athlete_trophy.date
        else
          trophy.update!(athlete:)
        end
      end
    end

    def schedule_telegram_notification
      return unless athlete.user_id

      inform_date = Date.current.noon.future? ? Date.current.noon : Date.tomorrow + 10.hours
      Telegram::Notification::AfterReuniteJob.set(wait_until: inform_date).perform_later(athlete.user_id)
    end
  end
end