testmycode/tmc-server

View on GitHub
app/models/awarded_point.rb

Summary

Maintainability
B
5 hrs
Test Coverage
# frozen_string_literal: true

require 'point_comparison'

# Stores when a point (course_id, name) has been awared to a particular user.
#
# There is a reference to the submission that first awarded the point, but this
# reference can be nil if the submission has been deleted.

class AwardedPoint < ApplicationRecord
  include PointComparison
  include Swagger::Blocks

  swagger_schema :AwardedPoint do
    key :required, %i[id course_id user_id submission_id name created_at]

    property :id, type: :integer, example: 1
    property :course_id, type: :integer, example: 1
    property :user_id, type: :integer, example: 1
    property :submission_id, type: :integer, example: 2
    property :name, type: :string, example: 'point name'
    property :created_at, type: :string, example: '2016-10-17T11:10:17.295+03:00'
  end

  def point_as_json
    as_json only: %i[
      id
      course_id
      user_id
      submission_id
      name
      created_at
    ]
  end

  swagger_schema :AwardedPointWithExerciseId do
    key :required, %i[awarded_point exercise_id]

    property :awarded_point do
      key :"$ref", :AwardedPoint
    end
    property :exercise_id, type: :integer, example: 1
  end

  def self.as_json_with_exercise_ids(related_exercises)
    related_exercises = related_exercises.map { |e| ["#{e.course_id}-#{e.name}", e.id] }.to_h
    all.map do |p|
      { awarded_point: p.point_as_json, exercise_id: related_exercises["#{p.course_id}-#{p.submission.exercise_name}"] }
    end
  end

  belongs_to :course
  belongs_to :user
  belongs_to :submission, optional: true

  after_save :create_organization_membership
  after_save :kafka_update_points
  after_destroy :kafka_update_points

  def self.exercise_user_points(exercise, user)
    return none if exercise.hide_submission_results
    where(course_id: exercise.course_id, user_id: user.id)
      .joins(:submission)
      .where('submissions.course_id = ? AND submissions.exercise_name = ?',
             exercise.course_id, exercise.name)
  end

  def self.course_user_points(course, user)
    where(course_id: course.id, user_id: user.id)
  end

  def self.course_points(course, include_admins = false, hidden = false, only_for_user = nil)
    awarded_points = AwardedPoint.arel_table
    users = User.arel_table
    exercises = Exercise.arel_table
    submissions = Submission.arel_table
    courses = Course.arel_table

    query = awarded_points
            .project(awarded_points[:id].count.as('count'))
            .where(awarded_points[:course_id].eq(course.id))
            .join(submissions).on(awarded_points[:submission_id].eq(submissions[:id]))
            .join(courses).on(submissions[:course_id].eq(courses[:id]))
            .join(exercises, Arel::Nodes::OuterJoin).on(
              courses[:id].eq(exercises[:course_id])
                    .and(submissions[:exercise_name].eq(exercises[:name]))
            )
    query = query.where(exercises[:hide_submission_results].eq(false).or(exercises[:id].eq(nil))) unless hidden

    if !include_admins && only_for_user
      query.join(users).on(users[:id].eq(awarded_points[:user_id]), users[:administrator].eq(false), users[:legitimate_student].eq(true)).where(users[:id].eq(only_for_user.id))
    elsif !include_admins
      query.join(users).on(users[:id].eq(awarded_points[:user_id]), users[:administrator].eq(false), users[:legitimate_student].eq(true))
    elsif only_for_user
      query.join(users).on(users[:id].eq(awarded_points[:user_id])).where(users[:id].eq(only_for_user.id))
    end

    res = ActiveRecord::Base.connection.execute(query.to_sql).to_a
    if !res.empty?
      res[0]['count'].to_i
    else
      Rails.logger.warn("No points found for course: #{course.id}")
      0
    end
  end

  def self.course_user_sheet_points(course, user, sheetname)
    course_user_points(course, user)
      .joins('INNER JOIN available_points ON available_points.name = awarded_points.name')
      .joins('INNER JOIN exercises ON available_points.exercise_id = exercises.id')
      .where(exercises: { gdocs_sheet: sheetname, course_id: course.id })
      .group('awarded_points.id')
  end

  def self.course_sheet_points(course, sheetnames, include_admins = false, only_for_user = nil)
    awarded_points = AwardedPoint.arel_table
    available_points = AvailablePoint.arel_table
    exercises = Exercise.arel_table
    users = User.arel_table
    query = awarded_points
            .project(awarded_points[:name].count.as('count'), exercises[:gdocs_sheet])
            .join(available_points).on(available_points[:name].eq(awarded_points[:name]))
            .join(exercises).on(available_points[:exercise_id].eq(exercises[:id]), exercises[:course_id].eq(course.id))
            .where(awarded_points[:course_id].eq(course.id))
            .where(exercises[:gdocs_sheet].in(sheetnames))
            .where(exercises[:course_id].eq(course.id))
            .group(exercises[:gdocs_sheet])

    if !include_admins && only_for_user
      query.join(users).on(users[:id].eq(awarded_points[:user_id]), users[:administrator].eq(false), users[:legitimate_student].eq(true)).where(users[:id].eq(only_for_user.id))
    elsif !include_admins
      query.join(users).on(users[:id].eq(awarded_points[:user_id]), users[:administrator].eq(false), users[:legitimate_student].eq(true))
    elsif only_for_user
      query.join(users).on(users[:id].eq(awarded_points[:user_id])).where(users[:id].eq(only_for_user.id))
    end

    res = {}
    ActiveRecord::Base.connection.execute(query.to_sql).map do |record|
      res[record['gdocs_sheet']] = record['count'].to_i
    end
    res
  end

  # Loads users that have any points for the course/sheet
  def self.users_in_course_with_sheet(course, sheetname)
    users = User.arel_table

    sql = per_user_in_course_with_sheet_query(course, sheetname, hidden: false)
          .project(users[:id].as('uid'))
          .to_sql

    uids = ActiveRecord::Base.connection.execute(sql).map { |record| record['uid'] }
    User.where(id: uids)
  end

  # Gets a hash of user to array of point names awarded for exercises of the given sheet
  def self.per_user_in_course_with_sheet(course, sheetname, opts = {})
    users = User.arel_table
    awarded_points = AwardedPoint.arel_table

    sql = per_user_in_course_with_sheet_query(course, sheetname, opts[:hidden])
          .project([users[:login].as('username'), awarded_points[:name].as('name')])
          .to_sql

    result = {}
    ActiveRecord::Base.connection.execute(sql).each do |record|
      result[record['username']] ||= []
      result[record['username']] << record['name']
    end
    result.default = []
    result
  end

  # Gets a hash of user to count of points awarded for exercises of the given sheet
  # TODO find users, shttename -> sheetnames
  def self.count_per_user_in_course_with_sheet(course, sheetnames, only_for_user = nil, hidden = false)
    users = User.arel_table
    exercises = Exercise.arel_table

    query = per_user_in_course_with_sheet_query(course, sheetnames, hidden)
            .project(users[:login].as('username'), users[:login].count.as('count'), exercises[:gdocs_sheet])
            .group(users[:login], exercises[:gdocs_sheet])

    query.where(users[:id].eq(only_for_user.id)) if only_for_user

    result = {}
    ActiveRecord::Base.connection.execute(query.to_sql).each do |record|
      result[record['username']] ||= {}
      result[record['username']][record['gdocs_sheet']] ||= 0
      result[record['username']][record['gdocs_sheet']] = record['count'].to_i
    end
    result
  end

  def self.all_awarded(user)
    awarded_points = AwardedPoint.arel_table
    exercises = Exercise.arel_table
    submissions = Submission.arel_table
    courses = Course.arel_table

    awarded_query = awarded_points
                    .project(awarded_points[:id])
                    .where(awarded_points[:user_id].eq(user.id))
                    .where(exercises[:hide_submission_results].eq(false).or(exercises[:id].eq(nil)))
                    .join(submissions).on(awarded_points[:submission_id].eq(submissions[:id]))
                    .join(courses).on(submissions[:course_id].eq(courses[:id]))
                    .join(exercises, Arel::Nodes::OuterJoin).on(
                      courses[:id].eq(exercises[:course_id])
                            .and(submissions[:exercise_name].eq(exercises[:name]))
                    )
    ActiveRecord::Base.connection.execute(awarded_query.to_sql).to_a.map { |h| h['id'] }
  end

  private_class_method def self.without_admins(query)
    query.joins('INNER JOIN users ON users.id = awarded_points.user_id').where(users: { administrator: false })
  end

  private_class_method def self.per_user_in_course_with_sheet_query(course, sheetnames, hidden = false)
    users = User.arel_table
    awarded_points = AwardedPoint.arel_table
    available_points = AvailablePoint.arel_table
    exercises = Exercise.arel_table

    q = awarded_points
        .join(users).on(awarded_points[:user_id].eq(users[:id]))
        .join(available_points).on(available_points[:name].eq(awarded_points[:name]))
        .join(exercises, Arel::Nodes::OuterJoin).on(available_points[:exercise_id].eq(exercises[:id]))
        .where(awarded_points[:course_id].eq(course.id))
        .where(awarded_points[:user_id].eq(users[:id]))
        .where(exercises[:course_id].eq(course.id))
        .where(exercises[:gdocs_sheet].in(sheetnames))
        .where(awarded_points[:course_id].eq(course.id))
        .where(awarded_points[:user_id].eq(users[:id]))

    q = q.where(exercises[:hide_submission_results].eq(false).or(exercises[:id].eq(nil))) unless hidden
    q
  end

  private
    def kafka_update_points
      return if !self.course.moocfi_id || self.course.moocfi_id.blank?
      exercise = self.submission.exercise
      KafkaBatchUpdatePoints.create!(course_id: self.course_id, user_id: self.user_id, exercise_id: exercise.id, task_type: 'user_progress')
      KafkaBatchUpdatePoints.create!(course_id: self.course_id, user_id: self.user_id, exercise_id: exercise.id, task_type: 'user_points')
    end

    def create_organization_membership
      organization = self.course.organization
      user = self.user
      OrganizationMembership.create!(user: user, organization: organization) unless organization.member?(user)
    end
end