pupilfirst/pupilfirst

View on GitHub
app/presenters/courses/leaderboard_presenter.rb

Summary

Maintainability
B
4 hrs
Test Coverage
B
85%
module Courses
  class LeaderboardPresenter < ApplicationPresenter
    class Student < SimpleDelegator
      attr_reader :score, :rank
      attr_accessor :delta

      def initialize(student, score, rank, current_user)
        @score = score
        @rank = rank
        @current_user = current_user

        super(student)
      end

      def level_number
        @level_number ||= level.number
      end

      def current_student?
        @current_student ||= (user_id == @current_user&.id)
      end
    end

    def initialize(view_context, course, params_on)
      @course = course
      @params_on = params_on

      super(view_context)
    end

    def school_name
      @school_name ||=
        begin
          raise "current_school cannot be missing here" if current_school.blank?

          current_school.name
        end
    end

    def start_date
      lts.week_start.strftime("%B %-d")
    end

    def end_date
      lts.week_end.strftime("%B %-d")
    end

    def toppers
      @toppers ||= students.find_all { |e| e.rank == 1 }
    end

    def heading
      if current_user_is_topper?
        return(
          I18n.t("presenters.courses.leaderboard.heading.top_leaderboard_html")
        )
      end

      multiple_mid_text =
        I18n.t("presenters.courses.leaderboard.heading.multiple_mid_text")

      h =
        if toppers.count == 1
          "<span class='font-bold'>#{toppers.first.name}</span> #{I18n.t("presenters.courses.leaderboard.heading.top_week")} "
        elsif toppers.count < 4
          names = toppers.map { |s| "<span class='font-bold'>#{s.name}</span>" }
          "#{names.to_sentence} #{multiple_mid_text}"
        else
          others_count = toppers.count - 2
          names =
            toppers[0..1].map { |s| "<span class='font-bold'>#{s.name}</span>" }
          "#{names.join(", ")} #{I18n.t("shared.and")} <span class='font-bold'>#{others_count} #{I18n.t("presenters.courses.leaderboard.heading.others")}</span> #{multiple_mid_text}"
        end

      (h + "<span class='font-bold'>#{top_score}</span>.").html_safe
    end

    def students
      @students ||=
        begin
          current_leaderboard.map do |student_id, student|
            delta =
              if last_leaderboard[student_id].present?
                last_leaderboard[student_id].rank - student.rank
              end

            student.delta = delta
            student
          end
        end
    end

    def inactive_students_count
      @inactive_students_count ||= active_students.count - students.count
    end

    def previous_page?
      difference_in_days < 120
    end

    def next_page?
      difference_in_days.positive?
    end

    def previous_page_link
      "?on=#{(on - 1.week).strftime("%Y%m%d")}"
    end

    def next_page_link
      if difference_in_days < 8
        view.leaderboard_course_path
      else
        "?on=#{(on + 1.week).strftime("%Y%m%d")}"
      end
    end

    def course_entries(from, to)
      LeaderboardEntry.where(
        student: active_students,
        period_from: from,
        period_to: to
      ).includes(student: [user: { avatar_attachment: :blob }])
    end

    def rank_change_icon(delta)
      if delta >= 10
        view.image_tag(
          "courses/leaderboard/rank-change-up-double.svg",
          alt:
            I18n.t(
              "presenters.courses.leaderboard.rank_change_icon.rank_change_double_alt"
            )
        )
      elsif delta.positive?
        view.image_tag(
          "courses/leaderboard/rank-change-up.svg",
          alt:
            I18n.t(
              "presenters.courses.leaderboard.rank_change_icon.rank_up_alt"
            )
        )
      elsif delta.zero?
        view.image_tag(
          "courses/leaderboard/rank-no-change.svg",
          alt:
            I18n.t(
              "presenters.courses.leaderboard.rank_change_icon.rank_no_change_alt"
            )
        )
      elsif delta > -10
        view.image_tag(
          "courses/leaderboard/rank-change-down.svg",
          alt:
            I18n.t(
              "presenters.courses.leaderboard.rank_change_icon.rank_down_alt"
            )
        )
      else
        view.image_tag(
          "courses/leaderboard/rank-change-down-double.svg",
          alt:
            I18n.t(
              "presenters.courses.leaderboard.rank_change_icon.rank_down_double_alt"
            )
        )
      end
    end

    def format_delta(delta)
      return if delta.blank?

      delta.negative? ? delta * -1 : delta
    end

    private

    def difference_in_days
      @difference_in_days = (Time.zone.now.to_date - on.to_date).to_i
    end

    def top_score
      @top_score ||= toppers.first.score
    end

    def current_user_is_topper?
      current_user.present? && current_user.id.in?(toppers.map(&:user_id))
    end

    def on
      @on ||=
        begin
          if @params_on.present? &&
               @params_on.match?(
                 /\A20\d{2}(0[1-9]|1[012])(0[1-9]|[12][0-9]|3[01])\Z/
               )
            Time.zone.parse(@params_on).end_of_day
          else
            Time.zone.now
          end
        end
    end

    def lts
      @lts ||= LeaderboardTimeService.new(on)
    end

    def active_students
      @active_students ||=
        @course.students.active.where(excluded_from_leaderboard: false)
    end

    def current_leaderboard
      @current_leaderboard ||= ranked_students(lts.week_start, lts.week_end)
    end

    def last_leaderboard
      @last_leaderboard ||=
        ranked_students(lts.last_week_start, lts.last_week_end)
    end

    def ranked_students(from, to)
      last_rank = 0
      last_score = BigDecimal::INFINITY

      course_entries(from, to)
        .order(score: :DESC)
        .each_with_object({})
        .with_index(1) do |(entry, students), index|
          rank = entry.score < last_score ? index : last_rank

          students[entry.student.id] = Student.new(
            entry.student,
            entry.score,
            rank,
            current_user
          )

          last_rank = rank
          last_score = entry.score
        end
    end
  end
end