153 lines
6.4 KiB
Ruby
153 lines
6.4 KiB
Ruby
class DueDateCacher
|
|
def self.recompute(assignment)
|
|
new([assignment]).send_later_if_production_enqueue_args(:recompute,
|
|
:strand => "cached_due_date:calculator:#{assignment.context_type}:#{Shard.global_id_for(assignment.context_id)}")
|
|
end
|
|
|
|
def self.recompute_course(course, assignments = nil)
|
|
assignments ||= Assignment.where(context_id: course, context_type: 'Course').pluck(:id)
|
|
return if assignments.empty?
|
|
new(assignments).send_later_if_production_enqueue_args(:recompute,
|
|
:strand => "cached_due_date:calculator:Course:#{Shard.global_id_for(course)}")
|
|
end
|
|
|
|
def self.recompute_batch(assignments)
|
|
new(assignments).send_later_if_production_enqueue_args(:recompute,
|
|
:strand => "cached_due_date:calculator:batch:#{Shard.current.id}",
|
|
:priority => Delayed::LOWER_PRIORITY)
|
|
end
|
|
|
|
# expects all assignments to be on the same shard
|
|
def initialize(assignments)
|
|
@assignments = assignments
|
|
@shard = Shard.shard_for(assignments.first)
|
|
end
|
|
|
|
def shard
|
|
@shard
|
|
end
|
|
|
|
def submissions
|
|
Submission.where(:assignment_id => @assignments)
|
|
end
|
|
|
|
def create_overridden_submissions
|
|
# Get the students that have an overridden due date
|
|
overridden_students = Assignment.participants_with_overridden_due_at(@assignments)
|
|
return if overridden_students.length < 1
|
|
|
|
# Get default submission values.
|
|
default_submission = Submission.new
|
|
default_submission.infer_values
|
|
|
|
# Create insert scope
|
|
insert_scope = Course
|
|
.select("DISTINCT assignments.id, enrollments.user_id, '#{default_submission.workflow_state}', {{now}}, assignments.context_code, 0")
|
|
.joins("INNER JOIN assignments ON assignments.context_id = courses.id AND assignments.context_type = 'Course'
|
|
LEFT OUTER JOIN submissions ON submissions.user_id = enrollments.user_id AND submissions.assignment_id = assignments.id")
|
|
.joins(:current_enrollments)
|
|
.where("enrollments.user_id IN (?) AND assignments.id IN (?) AND submissions.id IS NULL", overridden_students, @assignments)
|
|
|
|
#Set timestamp syntax depending on the connection adapter
|
|
insert_sql = case ActiveRecord::Base.connection.adapter_name
|
|
when 'PostgreSQL'
|
|
insert_scope.to_sql.gsub("{{now}}", "now() AT TIME ZONE 'UTC'")
|
|
when 'MySQL', 'Mysql2'
|
|
insert_scope.to_sql.gsub("{{now}}", "utc_timestamp()")
|
|
when /sqlite/
|
|
insert_scope.to_sql.gsub("{{now}}", "datetime('now')")
|
|
end
|
|
|
|
# Create submissions that do not exist yet to calculate due dates for non submitted assignments.
|
|
Assignment.connection.update("INSERT INTO submissions (assignment_id, user_id, workflow_state, created_at, context_code, process_attempts) #{insert_sql}")
|
|
end
|
|
|
|
def recompute
|
|
# in a transaction on the correct shard:
|
|
shard.activate do
|
|
Assignment.transaction do
|
|
# Create overridden due date submissions
|
|
create_overridden_submissions
|
|
overrides = AssignmentOverride.active.overriding_due_at.where(:assignment_id => @assignments)
|
|
if overrides.exists?
|
|
# create temporary table
|
|
cast = Submission.connection.adapter_name == 'Mysql2' ? 'UNSIGNED INTEGER' : 'BOOL'
|
|
Assignment.connection.execute("CREATE TEMPORARY TABLE calculated_due_ats AS (#{submissions.select([
|
|
"submissions.id AS submission_id",
|
|
"submissions.user_id",
|
|
"submissions.assignment_id",
|
|
"assignments.due_at",
|
|
"CAST(#{Submission.sanitize(false)} AS #{cast}) AS overridden"
|
|
]).joins(:assignment).where(assignments: { id: @assignments }).to_sql})")
|
|
|
|
# create an ActiveRecord class around that temp table for the update_all
|
|
scope = Class.new(ActiveRecord::Base) do
|
|
self.table_name = :calculated_due_ats
|
|
self.primary_key = :submission_id
|
|
end
|
|
|
|
# for each override, narrow to the affected subset of the table, and
|
|
# apply
|
|
overrides.each do |override|
|
|
override_scope(scope, override).update_all(
|
|
:due_at => override.due_at,
|
|
:overridden => true)
|
|
end
|
|
|
|
# copy the results back to the submission table
|
|
submissions.
|
|
joins("INNER JOIN calculated_due_ats ON calculated_due_ats.submission_id=submissions.id").
|
|
where("cached_due_date<>calculated_due_ats.due_at OR (cached_due_date IS NULL)<>(calculated_due_ats.due_at IS NULL)").
|
|
update_all("cached_due_date=calculated_due_ats.due_at")
|
|
|
|
# clean up
|
|
temporary = "TEMPORARY " if Assignment.connection.adapter_name == 'Mysql2'
|
|
Assignment.connection.execute("DROP #{temporary}TABLE calculated_due_ats")
|
|
else
|
|
# just copy the assignment due dates to the submissions
|
|
submissions.
|
|
joins("INNER JOIN assignments ON assignments.id=submissions.assignment_id").
|
|
where("cached_due_date<>assignments.due_at OR (cached_due_date IS NULL)<>(assignments.due_at IS NULL)").
|
|
update_all("cached_due_date=assignments.due_at")
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
def override_scope(scope, override)
|
|
scope = scope.where(:assignment_id => override.assignment_id)
|
|
|
|
# and the override's due_at is more lenient than any existing overridden
|
|
# due_at
|
|
if override.due_at
|
|
scope = scope.where(
|
|
"NOT overridden OR (due_at IS NOT NULL AND due_at<?)",
|
|
override.due_at)
|
|
end
|
|
|
|
case override.set_type
|
|
when 'ADHOC'
|
|
# any student explicitly tagged by an adhoc override,
|
|
scope.joins("INNER JOIN assignment_override_students ON assignment_override_students.user_id=calculated_due_ats.user_id").
|
|
where(:assignment_override_students => {
|
|
:assignment_override_id => override
|
|
})
|
|
when 'CourseSection'
|
|
# any student in a section override's tagged section, or
|
|
scope.joins("INNER JOIN enrollments ON enrollments.user_id=calculated_due_ats.user_id").
|
|
where(:enrollments => {
|
|
:workflow_state => 'active',
|
|
:type => ['StudentEnrollment', 'StudentViewEnrollment'],
|
|
:course_section_id => override.set_id
|
|
})
|
|
when 'Group'
|
|
# any student in a group override's tagged group
|
|
scope.joins("INNER JOIN group_memberships ON group_memberships.user_id=calculated_due_ats.user_id").
|
|
where(:group_memberships => {
|
|
:workflow_state => 'accepted',
|
|
:group_id => override.set_id
|
|
})
|
|
end
|
|
end
|
|
end
|