diff --git a/Gemfile b/Gemfile index b6584b88802..c71e84471c6 100644 --- a/Gemfile +++ b/Gemfile @@ -36,6 +36,7 @@ gem 'sanitize', '1.2.1' gem 'uuid', '2.3.1' gem 'xml-simple', '1.0.12', :require => 'xmlsimple' gem 'will_paginate', '2.3.15' +gem 'hairtrigger', '0.1.1' group :test do gem 'sqlite3-ruby', '1.3.2' diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 76d33aee566..bce34250137 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -333,7 +333,7 @@ class ApplicationController < ActionController::Base @ungraded_assignments = @assignments.select{|a| a.grants_right?(@current_user, session, :grade) && a.expects_submission? && - a.submissions.having_submission.ungraded.count > 0 + a.needs_grading_count > 0 } @assignment_groups = @groups @past_assignments = @assignments.select{ |a| a.due_at && a.due_at < Time.now } diff --git a/app/models/assignment.rb b/app/models/assignment.rb index dcb3d8ae987..91e876ac33a 100644 --- a/app/models/assignment.rb +++ b/app/models/assignment.rb @@ -599,17 +599,10 @@ class Assignment < ActiveRecord::Base submission_types && self.submission_types != "" && self.submission_types != "none" && self.submission_types != 'not_graded' && self.submission_types != "online_quiz" && self.submission_types != 'discussion_topic' && self.submission_types != 'attendance' end - def ungraded_count - return read_attribute(:ungraded_count).to_i if read_attribute(:ungraded_count) - Rails.cache.fetch(['ungraded_count', self].cache_key) do - self.submissions.select{|s| s.ungraded? && s.has_submission? }.length #having_submission.ungraded.count - end - end - def graded_count return read_attribute(:graded_count).to_i if read_attribute(:graded_count) Rails.cache.fetch(['graded_count', self].cache_key) do - self.submissions.select{|s| !s.ungraded? }.length + self.submissions.select(&:graded?).length end end @@ -1067,13 +1060,6 @@ class Assignment < ActiveRecord::Base } } - named_scope :include_ungraded_count, lambda { - {:select => "assignments.*, (SELECT COUNT(*) FROM submissions - WHERE assignments.id = submissions.assignment_id - AND submissions.submission_type IS NOT NULL AND submissions.grade IS NULL) AS ungraded_count" - } - } - # don't really need this scope anymore since we are doing the auto_peer_reviews assigning as a delayed job instead of a poller, but I'll leave it here if it is useful to anyone. -RS named_scope :to_be_auto_peer_reviewed, lambda { {:conditions => ['assignments.peer_reviews_assigned != ? AND assignments.peer_reviews = ? AND assignments.due_at < ? AND assignments.automatic_peer_reviews = ?', true, true, Time.now.utc, true], :order => 'assignments.updated_at, assignments.peer_reviews_due_at' } @@ -1132,35 +1118,15 @@ class Assignment < ActiveRecord::Base } } - # This should only be used in the course drop down to show assignments recently graded. + # This should only be used in the course drop down to show assignments not yet graded. named_scope :need_grading_info, lambda{|limit, ignore_ids| ignore_ids ||= [] - # TODO: this could be faster using: - # SELECT assignments.id, title, points_possible, due_at, context_id, context_type, COUNT(submissions.id) AS need_graded_count - # FROM assignments - # JOIN submissions ON submissions.assignment_id = assignments.id - # JOIN enrollments ON enrollments.course_id = assignments.context_id AND enrollments.user_id = submissions.user_id AND enrollments.workflow_state != 'deleted' - # WHERE submission_types IS NOT NULL AND (submission_types NOT IN ('', 'none', 'not_graded', 'on_paper') AND (assignments.workflow_state != 'deleted')) AND (assignments.context_code IN ('course_20033','course_20036','course_20222','course_20237','course_20246','course_20247','course_20252','course_20257','course_20262','course_20263','course_20264','course_20265','course_20267','course_20268','course_20269','course_20270','course_20271','course_20272','course_20273','course_20276','course_20277','course_20281','course_20320','course_20325','course_20331','course_20341','course_20355','course_20363','course_20370','course_20428')) - # AND submissions.submitted_at > '2010-04-15 21:46:19' AND submissions.score IS NULL AND submissions.workflow_state != 'graded' - # GROUP BY assignments.id - # ORDER BY due_at DESC LIMIT 15 - - sql_for_need_graded_count = "(SELECT COUNT(submissions.id) FROM submissions - JOIN enrollments ON enrollments.user_id = submissions.user_id AND enrollments.workflow_state = 'active' - WHERE assignment_id = assignments.id - AND enrollments.course_id = assignments.context_id - AND ( - (score IS NULL AND submissions.workflow_state != 'graded') - OR submissions.workflow_state = 'submitted' - OR submissions.workflow_state = 'pending_review' - ) - AND submission_type IS NOT NULL)" - {:select => 'assignments.id, title, points_possible, due_at, context_id, context_type, submission_types, ' + - '(SELECT name FROM courses WHERE id = assignments.context_id) AS context_name,' + - "#{sql_for_need_graded_count} AS need_graded_count", - :conditions => "#{sql_for_need_graded_count} > 0 #{ignore_ids.empty? ? "" : "AND id NOT IN (#{ignore_ids.join(',')})"}", - :limit => limit, - :order=>'due_at ASC' + { + :select => 'assignments.id, title, points_possible, due_at, context_id, context_type, submission_types, ' + + '(SELECT name FROM courses WHERE id = assignments.context_id) AS context_name, needs_grading_count', + :conditions => "needs_grading_count > 0 #{ignore_ids.empty? ? "" : "AND id NOT IN (#{ignore_ids.join(',')})"}", + :limit => limit, + :order=>'due_at ASC' } } @@ -1434,10 +1400,6 @@ class Assignment < ActiveRecord::Base end end - def submissions_needing_grading - self.submissions.select{|s| !s.graded? } - end - def special_class; nil; end def submission_action_string diff --git a/app/models/enrollment.rb b/app/models/enrollment.rb index 98f46f9b2f8..1eccc1bfefd 100644 --- a/app/models/enrollment.rb +++ b/app/models/enrollment.rb @@ -38,6 +38,32 @@ class Enrollment < ActiveRecord::Base after_save :touch_user after_create :update_user_account_associations + trigger.after(:insert).where("NEW.workflow_state = 'active'") do + <<-SQL + UPDATE assignments + SET needs_grading_count = needs_grading_count + 1 + WHERE id IN (SELECT assignment_id + FROM submissions + WHERE user_id = NEW.user_id + AND context_code = 'course_' || NEW.course_id + AND (#{Submission.needs_grading_conditions}) + ); + SQL + end + + trigger.after(:update).where("NEW.workflow_state <> OLD.workflow_state AND (NEW.workflow_state = 'active' OR OLD.workflow_state = 'active')") do + <<-SQL + UPDATE assignments + SET needs_grading_count = needs_grading_count + CASE WHEN NEW.workflow_state = 'active' THEN 1 ELSE -1 END + WHERE id IN (SELECT assignment_id + FROM submissions + WHERE user_id = NEW.user_id + AND context_code = 'course_' || NEW.course_id + AND (#{Submission.needs_grading_conditions}) + ); + SQL + end + adheres_to_policy @@ -111,7 +137,7 @@ class Enrollment < ActiveRecord::Base def update_user_account_associations self.user.send_later(:update_account_associations) end - + def conclude self.workflow_state = "completed" self.completed_at = Time.now diff --git a/app/models/submission.rb b/app/models/submission.rb index f1f9811e7f9..543aabc0ec7 100644 --- a/app/models/submission.rb +++ b/app/models/submission.rb @@ -71,6 +71,19 @@ class Submission < ActiveRecord::Base } } + named_scope :needs_grading, :conditions => <<-SQL + submissions.submission_type IS NOT NULL + AND ( + submissions.score IS NULL + OR NOT submissions.grade_matches_current_submission + OR submissions.workflow_state IN ('submitted', 'pending_review') + ) + SQL + def self.needs_grading_conditions(prefix = nil) + conditions = needs_grading.proxy_options[:conditions].gsub(/\s+/, ' ') + conditions.gsub!("submissions.", prefix + ".") if prefix + conditions + end sanitize_field :body, Instructure::SanitizeField::SANITIZE @@ -86,6 +99,16 @@ class Submission < ActiveRecord::Base after_save :update_final_score after_save :submit_to_turnitin_later after_save :update_admins_if_just_submitted + + trigger.after(:update) do |t| + t.where('(#{Submission.needs_grading_conditions("OLD")}) <> (#{Submission.needs_grading_conditions("NEW")})') do + <<-SQL + UPDATE assignments + SET needs_grading_count = needs_grading_count + CASE WHEN (#{needs_grading_conditions('NEW')}) THEN 1 ELSE -1 END + WHERE id = NEW.assignment_id; + SQL + end + end attr_reader :suppress_broadcast attr_reader :group_broadcast_submission @@ -343,7 +366,7 @@ class Submission < ActiveRecord::Base true end attr_accessor :created_correctly_from_assignment_rb - + def update_admins_if_just_submitted if @just_submitted context.send_later_if_production(:resubmission_for, "assignment_#{assignment_id}") @@ -537,11 +560,7 @@ class Submission < ActiveRecord::Base workflow do state :submitted do - event :grade_it, :transitions_to => :graded do - set_broadcast_flags - self.workflow_state = :graded - broadcast_notifications - end + event :grade_it, :transitions_to => :graded end state :unsubmitted state :pending_review @@ -555,9 +574,6 @@ class Submission < ActiveRecord::Base named_scope :ungraded, lambda { {:conditions => ['submissions.grade IS NULL'], :include => :assignment} } - named_scope :ungraded_or_needing_regrade, lambda { - {:conditions => ['submissions.grade IS NULL OR grade_matches_current_submission = ?', false], :include => :assignment } - } named_scope :having_submission, lambda { {:conditions => ['submissions.submission_type IS NOT NULL'] } } @@ -590,10 +606,10 @@ class Submission < ActiveRecord::Base {:conditions => ['submissions.submission_type = ? AND submissions.attachment_id IS NULL AND submissions.process_attempts < 3', 'online_url'], :order => :updated_at} } - def ungraded? - !self.grade + def needs_regrading? + graded? && !grade_matches_current_submission? end - + def readable_state if workflow_state == 'pending_review' 'pending review' diff --git a/app/views/assignments/_assignments_list_content.html.erb b/app/views/assignments/_assignments_list_content.html.erb index 623e50767b2..511289a1b1a 100644 --- a/app/views/assignments/_assignments_list_content.html.erb +++ b/app/views/assignments/_assignments_list_content.html.erb @@ -69,9 +69,9 @@ $(document).ready(function() { } if(submission && submission.submission_type && !submission.score) { $assignment.addClass('group_assignment_ungraded'); - var cnt = parseInt($assignment.find(".ungraded_count").text(), 10) || 0; + var cnt = parseInt($assignment.find(".needs_grading_count").text(), 10) || 0; cnt++; - $assignment.find(".ungraded_count").text(cnt); + $assignment.find(".needs_grading_count").text(cnt); } $assignment.fillTemplateData({ data: submission, @@ -114,8 +114,8 @@ $(document).ready(function() { if($(this).hasClass('group_assignment_overdue')) { $(this).attr('title', "This assignment is overdue"); } else if($(this).hasClass('group_assignment_ungraded')) { - var ungraded_count = $(this).getTemplateData({textValues: ['ungraded_count']}).ungraded_count; - $(this).attr('title', ungraded_count + " submissions for this assignment still need grading"); + var needs_grading_count = $(this).getTemplateData({textValues: ['needs_grading_count']}).needs_grading_count; + $(this).attr('title', needs_grading_count + " submissions for this assignment still need grading"); } else if($(this).hasClass('group_assignment_graded')) { $(this).attr('title', "This assignment has been graded"); } diff --git a/app/views/assignments/_grade_assignment.html.erb b/app/views/assignments/_grade_assignment.html.erb index 2c35c848540..bfc48207f77 100644 --- a/app/views/assignments/_grade_assignment.html.erb +++ b/app/views/assignments/_grade_assignment.html.erb @@ -5,11 +5,15 @@ <% subs = @current_student_submissions %> <% unless @current_student_submissions.blank? %>