add soft-deletion to rubric associations

closes EVAL-812
flag=none

Test Plan 1: Deleting and Restoring a Rubric Association
1. Create a rubric in a course and attach it to an assignment.
2. In SpeedGrader, use the rubric to assess a couple students.
3. On the assignment show page, click the trash can icon on the
   rubric to delete the rubric association with the assignment (it
   says "Delete Rubric" but it's really just deleting the Rubric
   Association).
4. Go back to SpeedGrader and verify the rubric does not show up
   for the students that you assessed (the final grade should still
   be awarded, if you awarded one separate from the rubric).
5. Verify the rubric itself still exists at /courses/:id/rubrics
6. Go to /courses/:id/undelete and verify the RubricAssociation that
   was deleted is listed there. Click 'restore'.
7. Go back to the assignment show page and note that the rubric is
   now showing again.
8. Go back to SpeedGrader and note that the rubric assessments have
   returned. Hallelujah!

Test Plan 2: Deleting and Restoring a Rubric
1. Create a rubric in a course and attach it to an assignment.
2. In SpeedGrader, use the rubric to assess a couple students.
3. Go to /courses/:id/rubrics, click on the rubric, and then click
   'Delete Rubric'.
4. Go to the assignment show page and verify the rubric does not
   show as attached to the assignment any more.
5. Go back to SpeedGrader and verify the rubric does not show up
   for the students that you assessed (the final grade should still
   be awarded, if you awarded one separate from the rubric).
6. Go to /courses/:id/undelete and verify the Rubric that
   was deleted is listed there. Click 'restore'. You can also verify
   that the RubricAssociation for the assignment is listed on the
   page as well, though you don't need to click 'Restore' for that
   item.
7. Go back to the assignment show page and note that the rubric is
   now showing again.
8. Go back to SpeedGrader and note that the rubric assessments have
   returned. Hallelujah!

* Edge case testing: test account rubrics, and using rubrics that
  were created in other courses.

Change-Id: I02745619457f99f760bd9459ab9d85d23bba57bc
Reviewed-on: https://gerrit.instructure.com/c/canvas-lms/+/253739
Tested-by: Service Cloud Jenkins <svc.cloudjenkins@instructure.com>
QA-Review: Kai Bjorkman <kbjorkman@instructure.com>
Product-Review: Syed Hussain <shussain@instructure.com>
Reviewed-by: Adrian Packel <apackel@instructure.com>
Reviewed-by: Gary Mei <gmei@instructure.com>
This commit is contained in:
Spencer Olson 2020-11-24 12:46:25 -06:00
parent d720d0d122
commit c2f17da038
41 changed files with 475 additions and 116 deletions

View File

@ -340,11 +340,15 @@ class ContextController < ApplicationController
WORKFLOW_TYPES = [:all_discussion_topics, :assignment_groups, :assignments,
:collaborations, :context_modules, :enrollments, :groups,
:quizzes, :rubrics, :wiki_pages].freeze
:quizzes, :rubrics, :wiki_pages, :rubric_associations_with_deleted].freeze
ITEM_TYPES = WORKFLOW_TYPES + [:attachments, :all_group_categories].freeze
def undelete_index
if authorized_action(@context, @current_user, :manage_content)
@item_types = WORKFLOW_TYPES.select { |type| @context.class.reflections.key?(type.to_s) }.map { |type| @context.association(type).reader }
@item_types = WORKFLOW_TYPES.each_with_object([]) do |workflow_type, item_types|
if @context.class.reflections.key?(workflow_type.to_s)
item_types << @context.association(workflow_type).reader
end
end
@deleted_items = []
@item_types.each do |scope|
@ -369,6 +373,7 @@ class ContextController < ApplicationController
return render_unauthorized_action unless @context.grants_right?(@current_user, :manage_groups)
end
type = type.pluralize
type = 'rubric_associations_with_deleted' if type == 'rubric_associations'
raise "invalid type" unless ITEM_TYPES.include?(type.to_sym) && scope.class.reflections.key?(type)
@item = scope.association(type).reader.find(id)

View File

@ -957,7 +957,7 @@ class GradebooksController < ApplicationController
env[:student_group_reason_for_change] = updated_group_info.reason_for_change if updated_group_info.reason_for_change.present?
end
if @assignment.rubric_association
if @assignment.active_rubric_association?
env[:update_rubric_assessment_url] = context_url(
@context,
:context_rubric_association_rubric_assessments_url,

View File

@ -131,8 +131,8 @@ class RubricAssociationsController < ApplicationController
# If the rubric wasn't created as a general course rubric,
# and this was the last place it was being used in the course,
# go ahead and delete the rubric from the course.
association_count = RubricAssociation.where(:context_id => @context, :context_type => @context.class.to_s, :rubric_id => @rubric).for_grading.count
if !RubricAssociation.for_purpose('bookmark').where(rubric_id: @rubric).first && association_count == 0
association_count = RubricAssociation.active.where(:context_id => @context, :context_type => @context.class.to_s, :rubric_id => @rubric).for_grading.count
if !RubricAssociation.active.for_purpose('bookmark').where(rubric_id: @rubric).first && association_count == 0
@rubric.destroy_for(@context, current_user: @current_user)
end
render :json => @association

View File

@ -191,7 +191,7 @@ class RubricsController < ApplicationController
#
# @returns Rubric
def destroy
@rubric = RubricAssociation.where(rubric_id: params[:id], context_id: @context, context_type: @context.class.to_s).first.rubric
@rubric = RubricAssociation.active.where(rubric_id: params[:id], context_id: @context, context_type: @context.class.to_s).first.rubric
if authorized_action(@rubric, @current_user, :delete_associations) && authorized_action(@context, @current_user, :manage_rubrics)
@rubric.destroy_for(@context, current_user: @current_user)
render :json => @rubric

View File

@ -838,7 +838,7 @@ class SubmissionsApiController < ApplicationController
end
assessment = params[:rubric_assessment]
if assessment.is_a?(ActionController::Parameters) && @assignment.rubric_association
if assessment.is_a?(ActionController::Parameters) && @assignment.active_rubric_association?
if (assessment.keys & @assignment.rubric_association.rubric.criteria_object.map{|c| c.id.to_s}).empty?
return render :json => {:message => "invalid rubric_assessment"}, :status => :bad_request
end

View File

@ -58,14 +58,14 @@ class AssessmentRequest < ActiveRecord::Base
p.dispatch :rubric_assessment_submission_reminder
p.to { self.assessor }
p.whenever { |record|
record.assigned? && @send_reminder && rubric_association
record.assigned? && @send_reminder && active_rubric_association?
}
p.data { course_broadcast_data }
p.dispatch :peer_review_invitation
p.to { self.assessor }
p.whenever { |record|
send_notification = record.assigned? && @send_reminder && !rubric_association
send_notification = record.assigned? && @send_reminder && !active_rubric_association?
# Do not send notifications if the context is an unpublished course
# or if the asset is a submission and the assignment is unpublished
send_notification = false if self.context.is_a?(Course) && !self.context.workflow_state.in?(['available', 'completed'])
@ -147,7 +147,7 @@ class AssessmentRequest < ActiveRecord::Base
end
def comment_added(comment)
self.workflow_state = "completed" unless self.rubric_association && self.rubric_association.rubric
self.workflow_state = "completed" unless active_rubric_association? && self.rubric_association.rubric
end
def asset_user_name
@ -162,4 +162,8 @@ class AssessmentRequest < ActiveRecord::Base
override.update(marked_complete: true) if override.present?
end
end
def active_rubric_association?
!!self.rubric_association&.active?
end
end

View File

@ -82,7 +82,7 @@ class Assignment < ActiveRecord::Base
has_one :wiki_page
has_many :learning_outcome_alignments, -> { where("content_tags.tag_type='learning_outcome' AND content_tags.workflow_state<>'deleted'").preload(:learning_outcome) }, as: :content, inverse_of: :content, class_name: 'ContentTag'
has_one :rubric_association, -> { where(purpose: 'grading').order(:created_at).preload(:rubric) }, as: :association, inverse_of: :association_object
has_one :rubric, :through => :rubric_association
has_one :rubric, -> { merge(RubricAssociation.active) }, :through => :rubric_association
has_one :teacher_enrollment, -> { preload(:user).where(enrollments: { workflow_state: 'active', type: 'TeacherEnrollment' }) }, class_name: 'TeacherEnrollment', foreign_key: 'course_id', primary_key: 'context_id'
has_many :ignores, :as => :asset
has_many :moderated_grading_selections, class_name: 'ModeratedGrading::Selection'
@ -297,7 +297,7 @@ class Assignment < ActiveRecord::Base
# Learning outcome alignments seem to get copied magically, possibly
# through the rubric
if self.rubric_association
if active_rubric_association?
result.rubric_association = self.rubric_association.clone
result.rubric_association.skip_updating_points_possible = true
end
@ -1261,7 +1261,7 @@ class Assignment < ActiveRecord::Base
def destroy
self.workflow_state = 'deleted'
ContentTag.delete_for(self)
self.rubric_association.destroy if self.rubric_association.present?
self.rubric_association.destroy if active_rubric_association?
self.save!
each_submission_type { |submission| submission.destroy if submission && !submission.deleted? }
@ -2073,7 +2073,7 @@ class Assignment < ActiveRecord::Base
if opts[:comment] && opts[:assessment_request]
# if there is no rubric the peer review is complete with just a comment
opts[:assessment_request].complete unless opts[:assessment_request].rubric_association
opts[:assessment_request].complete unless opts[:assessment_request].active_rubric_association?
end
# commenting on a student submission results in a teacher occupying a
@ -2209,7 +2209,9 @@ class Assignment < ActiveRecord::Base
def as_json(options={})
json = super(options)
if json && json['assignment']
return json unless json
if json['assignment']
# remove anything coming automatically from deprecated db column
json['assignment'].delete('group_category')
if self.group_category
@ -2219,7 +2221,16 @@ class Assignment < ActiveRecord::Base
# or failing that, version from query
json['assignment']['group_category'] = self.read_attribute('group_category')
end
if json.dig('assignment', 'rubric_association') && !active_rubric_association?
json['assignment'].delete('rubric_association')
end
end
if json['rubric_association'] && !active_rubric_association?
json.delete('rubric_association')
end
json
end
@ -2375,7 +2386,7 @@ class Assignment < ActiveRecord::Base
private :visible_students_for_speed_grader
def visible_rubric_assessments_for(user, opts={})
return [] unless user && self.rubric_association
return [] unless user && active_rubric_association?
scope = self.rubric_association.rubric_assessments.preload(:assessor)
@ -3585,6 +3596,10 @@ class Assignment < ActiveRecord::Base
self.find_by(lti_context_id: lti_context_id)
end
def active_rubric_association?
!!self.rubric_association&.active?
end
private
def set_muted

View File

@ -495,7 +495,7 @@ class ContentTag < ActiveRecord::Base
end
def has_rubric_association?
content.respond_to?(:rubric_association) && content.rubric_association
content.respond_to?(:rubric_association) && !!content.rubric_association&.active?
end
scope :for_tagged_url, lambda { |url, tag| where(:url => url, :tag => tag) }

View File

@ -85,7 +85,7 @@ module Context
end
def self.sorted_rubrics(user, context)
associations = RubricAssociation.bookmarked.for_context_codes(context.asset_string).preload(:rubric => :context)
associations = RubricAssociation.active.bookmarked.for_context_codes(context.asset_string).preload(:rubric => :context)
Canvas::ICU.collate_by(associations.to_a.uniq(&:rubric_id).select{|r| r.rubric }) { |r| r.rubric.title || CanvasSort::Last }
end
@ -102,7 +102,7 @@ module Context
context_codes << context.asset_string if context
end
end
associations += RubricAssociation.bookmarked.for_context_codes(context_codes).include_rubric.preload(:context).to_a
associations += RubricAssociation.active.bookmarked.for_context_codes(context_codes).include_rubric.preload(:context).to_a
end
associations = associations.select(&:rubric).uniq{|a| [a.rubric_id, a.context.asset_string] }

View File

@ -139,7 +139,7 @@ class LearningOutcomeResult < ActiveRecord::Base
# rubocop:disable Metrics/LineLength
scope :exclude_muted_associations, -> {
joins("LEFT JOIN #{RubricAssociation.quoted_table_name} rassoc ON rassoc.id = learning_outcome_results.association_id AND learning_outcome_results.association_type = 'RubricAssociation'").
joins("LEFT JOIN #{Assignment.quoted_table_name} ra ON ra.id = rassoc.association_id AND rassoc.association_type = 'Assignment' AND rassoc.purpose = 'grading'").
joins("LEFT JOIN #{Assignment.quoted_table_name} ra ON ra.id = rassoc.association_id AND rassoc.association_type = 'Assignment' AND rassoc.purpose = 'grading' AND rassoc.workflow_state = 'active'").
joins("LEFT JOIN #{Quizzes::Quiz.quoted_table_name} ON quizzes.id = learning_outcome_results.association_id AND learning_outcome_results.association_type = 'Quizzes::Quiz'").
joins("LEFT JOIN #{Assignment.quoted_table_name} qa ON qa.id = quizzes.assignment_id").
joins("LEFT JOIN #{Assignment.quoted_table_name} sa ON sa.id = learning_outcome_results.association_id AND learning_outcome_results.association_type = 'Assignment'").

View File

@ -185,7 +185,7 @@ class ModeratedGrading::ProvisionalGrade < ActiveRecord::Base
def publish_rubric_assessments!
self.rubric_assessments.each do |provisional_assessment|
rubric_association = provisional_assessment.rubric_association
rubric_association = provisional_assessment.active_rubric_association? ? provisional_assessment.rubric_association : nil
# This case arises when a rubric is deleted.
next if rubric_association.nil?

View File

@ -28,7 +28,8 @@ class Rubric < ActiveRecord::Base
belongs_to :user
belongs_to :rubric # based on another rubric
belongs_to :context, polymorphic: [:course, :account]
has_many :rubric_associations, :class_name => 'RubricAssociation', :dependent => :destroy
has_many :rubric_associations, -> { where(workflow_state: "active") }, class_name: 'RubricAssociation', inverse_of: :rubric, dependent: :destroy
has_many :rubric_associations_with_deleted, class_name: 'RubricAssociation', inverse_of: :rubric
has_many :rubric_assessments, :through => :rubric_associations, :dependent => :destroy
has_many :learning_outcome_alignments, -> { where("content_tags.tag_type='learning_outcome' AND content_tags.workflow_state<>'deleted'").preload(:learning_outcome) }, as: :content, inverse_of: :content, class_name: 'ContentTag'
@ -99,6 +100,7 @@ class Rubric < ActiveRecord::Base
LEFT JOIN #{RubricAssociation.quoted_table_name} associations_for_count
ON rubrics.id = associations_for_count.rubric_id
AND associations_for_count.purpose = 'grading'
AND associations_for_count.workflow_state = 'active'
JOINS
group('rubrics.id').
having('COUNT(rubrics.id) < 2')
@ -109,6 +111,7 @@ class Rubric < ActiveRecord::Base
LEFT JOIN #{RubricAssociation.quoted_table_name} associations_for_unassessed
ON rubrics.id = associations_for_unassessed.rubric_id
AND associations_for_unassessed.purpose = 'grading'
AND associations_for_unassessed.workflow_state = 'active'
JOINS
joins(<<~JOINS).
LEFT JOIN #{RubricAssessment.quoted_table_name} assessments_for_unassessed
@ -137,14 +140,19 @@ class Rubric < ActiveRecord::Base
alias_method :destroy_permanently!, :destroy
def destroy
rubric_associations.update_all(:bookmarked => false, :updated_at => Time.now.utc)
self.workflow_state = 'deleted'
self.save
if self.save
rubric_associations.in_batches.destroy_all
true
end
end
def restore
self.workflow_state = 'active'
self.save
if self.save
rubric_associations_with_deleted.where(workflow_state: "deleted").find_each(&:restore)
true
end
end
# If any rubric_associations for a given context are marked as
@ -162,9 +170,10 @@ class Rubric < ActiveRecord::Base
association.destroy
end
else
ras.update_all(:bookmarked => false, :updated_at => Time.now.utc)
ras.destroy_all
end
unless rubric_associations.bookmarked.exists?
if rubric_associations.bookmarked.none?
self.destroy
end
end

View File

@ -55,7 +55,8 @@ class RubricAssessment < ActiveRecord::Base
def update_outcomes_for_assessment(outcome_ids=[])
return if outcome_ids.empty?
alignments = if self.rubric_association.present?
alignments = if active_rubric_association?
self.rubric_association.association_object.learning_outcome_alignments.where({
learning_outcome_id: outcome_ids
})
@ -143,7 +144,7 @@ class RubricAssessment < ActiveRecord::Base
def update_assessment_requests
requests = self.assessment_requests
if self.rubric_association.present?
if active_rubric_association?
requests += self.rubric_association.assessment_requests.where({
assessor_id: self.assessor_id,
asset_id: self.artifact_id,
@ -262,7 +263,8 @@ class RubricAssessment < ActiveRecord::Base
end
def considered_anonymous?
return false unless self.rubric_association.present?
return false unless active_rubric_association?
self.rubric_association.association_type == 'Assignment' &&
self.rubric_association.association_object.anonymous_peer_reviews?
end
@ -272,7 +274,7 @@ class RubricAssessment < ActiveRecord::Base
end
def related_group_submissions_and_assessments
if self.rubric_association && self.rubric_association.association_object.is_a?(Assignment) && !self.artifact.is_a?(ModeratedGrading::ProvisionalGrade) && !self.rubric_association.association_object.grade_group_students_individually
if active_rubric_association? && self.rubric_association.association_object.is_a?(Assignment) && !self.artifact.is_a?(ModeratedGrading::ProvisionalGrade) && !self.rubric_association.association_object.grade_group_students_individually
students = self.rubric_association.association_object.group_students(self.user).last
submissions = students.map do |student|
submission = self.rubric_association.association_object.find_asset_for_assessment(self.rubric_association, student).first
@ -286,4 +288,8 @@ class RubricAssessment < ActiveRecord::Base
def set_root_account_id
self.root_account_id ||= self.rubric&.root_account_id
end
def active_rubric_association?
!!self.rubric_association&.active?
end
end

View File

@ -23,7 +23,7 @@
# with this idea, such as assignment submissions.
# The other purpose of this class is just to make rubrics reusable.
class RubricAssociation < ActiveRecord::Base
include Workflow
include Canvas::SoftDeletable
attr_accessor :skip_updating_points_possible
attr_writer :updating_user
@ -34,13 +34,13 @@ class RubricAssociation < ActiveRecord::Base
polymorphic_prefix: :association
belongs_to :context, polymorphic: [:course, :account]
has_many :rubric_assessments, :dependent => :nullify
has_many :assessment_requests, :dependent => :nullify
has_many :rubric_assessments
has_many :assessment_requests
has_a_broadcast_policy
validates_presence_of :purpose, :rubric_id, :association_id, :association_type, :context_id, :context_type
validates :workflow_state, inclusion: {in: ["active"]}, allow_nil: true
validates :workflow_state, inclusion: {in: ["active", "deleted"]}
before_create :set_root_account_id
before_save :update_assignment_points
@ -63,10 +63,6 @@ class RubricAssociation < ActiveRecord::Base
before_destroy :record_deletion_audit_event
end
workflow do
state :active
end
ValidAssociationModels = {
'Course' => ::Course,
'Assignment' => ::Assignment,
@ -87,6 +83,11 @@ class RubricAssociation < ActiveRecord::Base
klass.where(id: a_id).first if a_id.present? # authorization is checked in the calling method
end
def restore
self.workflow_state = "active"
save
end
def course_broadcast_data
context.broadcast_data if context.is_a?(Course)
end
@ -111,7 +112,11 @@ class RubricAssociation < ActiveRecord::Base
def assert_uniqueness
if purpose == 'grading'
RubricAssociation.where(association_id: association_id, association_type: association_type, purpose: 'grading').each do |ra|
ra.destroy unless ra == self
next if ra == self
ra.rubric_assessments.update_all(rubric_association_id: nil)
ra.assessment_requests.update_all(rubric_association_id: nil)
ra.destroy_permanently!
end
end
end
@ -126,10 +131,8 @@ class RubricAssociation < ActiveRecord::Base
def update_alignments
return unless assignment
outcome_ids = []
unless self.destroyed?
outcome_ids = rubric.learning_outcome_alignments.map(&:learning_outcome_id)
end
outcome_ids = self.deleted? ? [] : rubric.learning_outcome_alignments.map(&:learning_outcome_id)
LearningOutcome.update_alignments(assignment, context, outcome_ids)
true
end
@ -390,7 +393,7 @@ class RubricAssociation < ActiveRecord::Base
private
def record_save_audit_event
existing_association = assignment.rubric_association
existing_association = assignment.active_rubric_association? ? assignment.rubric_association : nil
event_type = existing_association.present? ? 'rubric_updated' : 'rubric_created'
payload = if event_type == 'rubric_created'
{id: rubric_id}

View File

@ -58,6 +58,7 @@ module SpeedGrader
],
:include_root => false
)
res['context']['concluded'] = assignment.context.concluded?
res['anonymize_students'] = assignment.anonymize_students?
res['anonymize_graders'] = !assignment.can_view_other_grader_identities?(current_user)

View File

@ -104,7 +104,11 @@ class Submission < ActiveRecord::Base
has_many :attachment_associations, :as => :context, :inverse_of => :context
has_many :provisional_grades, class_name: 'ModeratedGrading::ProvisionalGrade'
has_many :originality_reports
has_one :rubric_assessment, -> { where(assessment_type: 'grading').where.not(rubric_association: nil) }, as: :artifact, inverse_of: :artifact
has_one :rubric_assessment, -> do
joins(:rubric_association).
where(assessment_type: 'grading').
where(rubric_associations: { workflow_state: 'active' })
end, as: :artifact, inverse_of: :artifact
has_one :lti_result, inverse_of: :submission, class_name: 'Lti::Result', dependent: :destroy
has_many :submission_drafts, inverse_of: :submission, dependent: :destroy
@ -2225,7 +2229,7 @@ class Submission < ActiveRecord::Base
@assessment_request_count ||= 0
@assessment_request_count += 1
user = obj.user rescue nil
association = self.assignment.rubric_association
association = self.assignment.active_rubric_association? ? self.assignment.rubric_association : nil
res = self.assessment_requests.where(assessor_asset_id: obj.id, assessor_asset_type: obj.class.to_s, assessor_id: user.id, rubric_association_id: association.try(:id)).
first_or_initialize
res.user_id = self.user_id
@ -2553,7 +2557,7 @@ class Submission < ActiveRecord::Base
end
def visible_rubric_assessments_for(viewing_user, attempt: nil)
return [] if assignment.rubric_association.blank?
return [] unless assignment.active_rubric_association?
unless posted? || grants_right?(viewing_user, :read_grade)
# If this submission is unposted and the viewer can't view the grade,
@ -2636,7 +2640,7 @@ class Submission < ActiveRecord::Base
submission.user = user
assessment = user_data[:rubric_assessment]
if assessment.is_a?(Hash) && assignment.rubric_association
if assessment.is_a?(Hash) && assignment.active_rubric_association?
# prepend each key with "criterion_", which is required by
# the current RubricAssociation#assess code.
assessment.keys.each do |crit_name|

View File

@ -53,7 +53,7 @@
<a
href="<%= context_url(@context, :context_rubrics_url) %>"
class="add_rubric_link Button icon-plus"
style="<%= hidden if @assignment.rubric_association %>"
style="<%= hidden if @assignment.active_rubric_association? %>"
>
<span aria-hidden="true"><%= t 'links.add_rubric', "Rubric" %></span>
<span class="screenreader-only"><%= t('Add Rubric') %></span>

View File

@ -47,7 +47,7 @@
<% js_bundle 'legacy/assignments_peer_reviews' %>
<h1><%= t :title, "%{assignment} Peer Reviews", :assignment => @assignment.title %></h1>
<% if @assignment.rubric_association %>
<% if @assignment.active_rubric_association? %>
<%= t :overview_with_rubric, "Student peer reviews will be considered complete when students have commented
at least once on the page and filled out the rubric form for the assignment." %>
<% else %>

View File

@ -193,7 +193,7 @@
<a
href="<%= context_url(@context, :context_rubrics_url) %>"
class="add_rubric_link Button icon-plus"
style="<%= hidden if @assignment.rubric_association %>"
style="<%= hidden if @assignment.active_rubric_association? %>"
>
<span aria-hidden="true"><%= t 'links.add_rubric', "Rubric" %></span>
<span class="screenreader-only"><%= t('Add Rubric') %></span>

View File

@ -564,7 +564,7 @@
<div class="rubric-toggle"><a href="#" data-aria="rubric_<%= assignment.id %>" class="screenreader-toggle pull-left"><%= t(:close_rubric, 'Close Rubric') %></a></div>
<% if @domain_root_account.feature_enabled?(:non_scoring_rubrics)%>
<div
class="react_rubric_container rubric <%= "for_grading" if assessment.rubric_association.try(:use_for_grading) %>"
class="react_rubric_container rubric <%= "for_grading" if assessment.active_rubric_association? && assessment.rubric_association.try(:use_for_grading) %>"
id="<%= assessment.rubric ? "rubric_#{assessment.rubric&.id}" : "default_rubric" %>"
data-rubric-id="<%= assessment.rubric ? assessment.rubric.id : "default" %>"
data-rubric-assessment-id="<%= assessment ? assessment.id : "none" %>"

View File

@ -188,7 +188,7 @@
</div>
<div class="content_box">
<% if @assignment.rubric_association %>
<% if @assignment.active_rubric_association? %>
<div id="rubric_full" style="display: none;">
<div id="rubric_full_resizer">
<div id="rubric_full_resizer_handle"></div>
@ -213,7 +213,7 @@
</div>
<% end %>
<% if @assignment.rubric_association || can_do(@context, @current_user, :manage_grades) || (@context.concluded? && can_do(@context, @current_user, :read_as_admin)) %>
<% if @assignment.active_rubric_association? || can_do(@context, @current_user, :manage_grades) || (@context.concluded? && can_do(@context, @current_user, :read_as_admin)) %>
<div id="grading">
<h2 class="gradebookHeader--rightside"><%= t('headers.assessment', "Assessment") %></h2>
<% if can_do(@context, @current_user, :manage_grades) || (@context.concluded? && can_do(@context, @current_user, :read_as_admin)) %>
@ -227,7 +227,7 @@
<div id="grading_details_mount_point"></div>
<% end %>
<% if @assignment.rubric_association %>
<% if @assignment.active_rubric_association? %>
<div id="rubric_summary_holder">
<div id="rubric_assessments_list_and_edit_button_holder">
<span id="rubric_assessments_list">

View File

@ -17,7 +17,7 @@
%>
<div id="rubrics" class="rubric_dialog" style="display: none; margin-bottom: 10px;">
<% if @assignment.rubric_association && @assignment.rubric_association.rubric %>
<% if @assignment.active_rubric_association? && @assignment.rubric_association.rubric %>
<%= render :partial => "shared/rubric", :object => @assignment.rubric_association.rubric,
:locals => {:association => @assignment,
:rubric_association => @assignment.rubric_association,
@ -31,17 +31,17 @@
</div>
<div class="assignment_points_possible" style="display: none;"><%= round_if_whole(@assignment.points_possible) %></div>
<% if can_do(@assignment, @current_user, :update) %>
<%= render :partial => "shared/rubric_dialog", :locals => {:assignment => @assignment, :rubric => @assignment.rubric_association && @assignment.rubric_association.rubric} %>
<%= render :partial => "shared/rubric_dialog", :locals => {:assignment => @assignment, :rubric => @assignment.active_rubric_association? ? @assignment.rubric_association.rubric : nil} %>
<% end %>
<%= render :partial => "shared/rubric_criterion_dialog" %>
<%= render :partial => "shared/find_outcome" %>
<%= render :partial => "shared/rubric", :object => nil, :locals => {:association => @assignment, :editable => can_do(@assignment, @current_user, :update), :has_assessments => false, :edit_view => true} %>
<% if can_do(@assignment, @current_user, :update) %>
<div style="text-align: center; font-size: 1.2em; margin-top: 10px; display: none;">
<a href="<%= context_url(@context, :context_rubrics_url) %>" class="add_rubric_link rubric" style="<%= hidden if @assignment.rubric_association %>"><%= t 'links.assign_rubric', "Assign Rubric" %></a>
<a href="<%= context_url(@context, :context_rubrics_url) %>" class="add_rubric_link rubric" style="<%= hidden if @assignment.active_rubric_association? %>"><%= t 'links.assign_rubric', "Assign Rubric" %></a>
</div>
<% end %>
<a href="#" class="btn add_rubric_link" style="margin-top: 20px; <%= hidden if @assignment && @assignment.rubric_association %>"><%= image_tag "rubric.png", :alt => '' %> <%= t 'links.add_rubric', "Add Rubric" %></a>
<a href="#" class="btn add_rubric_link" style="margin-top: 20px; <%= hidden if @assignment && @assignment.active_rubric_association? %>"><%= image_tag "rubric.png", :alt => '' %> <%= t 'links.add_rubric', "Add Rubric" %></a>
</div>
<script>

View File

@ -23,7 +23,7 @@
context = @context
context = context.context if context.is_a?(Group)
show_grading = !@assignment || !%w{online_quiz not_graded}.include?(@assignment.submission_types)
rubric_association = assessment.rubric_association if assessment
rubric_association = assessment.rubric_association if assessment&.active_rubric_association?
anonymize_student ||= false
%>
<% cache(['rubric_render4',

View File

@ -25,7 +25,7 @@
<% end %>
<div id="rubrics" style="margin-bottom: 10px;">
<% if @assignment.rubric_association && @assignment.rubric_association.rubric %>
<% if @assignment.active_rubric_association? && @assignment.rubric_association.rubric %>
<%= render :partial => "shared/rubric", :object => @assignment.rubric_association.rubric, :locals => {:association => @assignment, :rubric_association => @assignment.rubric_association, :editable => can_do(@assignment, @current_user, :update), :has_assessments => !@assignment.rubric_association.rubric_assessments.empty?, :edit_view => can_do(@assignment.rubric_association, @current_user, :update) } %>
<% end %>
<div style="display: none;" id="rubric_parameters">
@ -34,7 +34,7 @@
<input type="hidden" name="rubric_association[purpose]" value="grading"/>
</div>
<% if can_do(@assignment, @current_user, :update) %>
<%= render :partial => "shared/rubric_dialog", :locals => {:assignment => @assignment, :rubric => @assignment.rubric_association && @assignment.rubric_association.rubric} %>
<%= render :partial => "shared/rubric_dialog", :locals => {:assignment => @assignment, :rubric => @assignment.active_rubric_association? ? @assignment.rubric_association.rubric : nil} %>
<% end %>
<%= render :partial => "shared/rubric_criterion_dialog" %>
</div>
@ -44,7 +44,7 @@
<% if can_do(@assignment, @current_user, :update) %>
<div style="text-align: center; font-size: 1.2em; margin-top: 10px; display: none;">
<a href="<%= context_url(@context, :context_rubrics_url) %>" class="add_rubric_link rubric" style="<%= hidden if @assignment.rubric_association %>"><%= t 'links.assign_rubric', 'Assign Rubric' %></a>
<a href="<%= context_url(@context, :context_rubrics_url) %>" class="add_rubric_link rubric" style="<%= hidden if @assignment.active_rubric_association? %>"><%= t 'links.assign_rubric', 'Assign Rubric' %></a>
</div>
<% end %>
<%= render :partial => "shared/sequence_footer", :locals => {:asset => @assignment} if !@assignment.context_module_tags.empty? %>

View File

@ -174,7 +174,7 @@
<% end %>
<% end %>
<% end %>
<% if @assignment.rubric_association && (
<% if @assignment.active_rubric_association? && (
@submission.user_can_read_grade?(@current_user, session) ||
@assignment.rubric_association.user_can_assess_for?(assessor: @current_user, assessee: @submission.user) ||
@assignment.rubric_association.user_did_assess_for?(assessor: @current_user, assessee: @submission.user) ||
@ -232,7 +232,7 @@
<%= t('peer_review_steps_complete', 'You have finished the required steps for this peer review.') %>
</div>
<div class="assessment_request_incomplete_message" style="<%= hidden if @assessment_request.completed? %>">
<% if @assessment_request && @assessment_request.rubric_association %>
<% if @assessment_request && @assessment_request.active_rubric_association? %>
<%= mt('peer_review_not_done_rubric', "**This peer review is not finished yet.** For it to be considered finished, you need to leave at least one comment and fill out the rubric form to the right.")
%>
<% else %>
@ -245,7 +245,7 @@
<% end %>
</div>
<% if @assignment.rubric_association %>
<% if @assignment.active_rubric_association? %>
<% @visible_rubric_assessments ||= [] %>
<div id="rubric_holder" style="position: absolute; <%= direction('right') %>: 0px; padding: 5px; background-color: #fff; border: 1px solid #aaa; border-<%= direction('right') %>-width: 0; display: none;">
<a

View File

@ -267,7 +267,7 @@ module Api::V1::Assignment
end
unless opts[:exclude_response_fields].include?('rubric')
if assignment.rubric_association
if assignment.active_rubric_association?
hash['use_rubric_for_grading'] = !!assignment.rubric_association.use_for_grading
if assignment.rubric_association.rubric
hash['free_form_criterion_comments'] = !!assignment.rubric_association.rubric.free_form_criterion_comments

View File

@ -44,7 +44,7 @@ module Api::V1::RubricAssessment
json_attributes = API_ALLOWED_RUBRIC_ASSESSMENT_OUTPUT_FIELDS
hash = api_json(rubric_assessment, user, session, json_attributes)
hash['data'] = rubric_assessment.data if opts[:style] == "full"
if opts[:style] == "full" && rubric_assessment.rubric_association.present?
if opts[:style] == "full" && rubric_assessment.active_rubric_association?
hash['rubric_association'] = rubric_assessment.rubric_association.as_json['rubric_association']
end
hash['comments'] = rubric_assessment.data.map{|rad| rad[:comments]} if opts[:style] == "comments_only"

View File

@ -207,7 +207,7 @@ module CC
end
node.workflow_state assignment.workflow_state
if assignment.rubric
assoc = assignment.rubric_association
assoc = assignment.active_rubric_association? ? assignment.rubric_association : nil
node.rubric_identifierref key_generator.create_key(assignment.rubric)
if assignment.rubric && assignment.rubric.context != assignment.context
node.rubric_external_identifier assignment.rubric.id

View File

@ -21,7 +21,8 @@ module RubricContext
def self.included(klass)
if klass < ActiveRecord::Base
klass.has_many :rubrics, :as => :context, :inverse_of => :context
klass.has_many :rubric_associations, -> { preload(:rubric) }, as: :context, inverse_of: :context, dependent: :destroy
klass.has_many :rubric_associations_with_deleted, -> { preload(:rubric) }, as: :context, inverse_of: :context, class_name: "RubricAssociation"
klass.has_many :rubric_associations, -> { where(workflow_state: "active").preload(:rubric) }, as: :context, inverse_of: :context, dependent: :destroy
klass.send :include, InstanceMethods
end
end

View File

@ -3741,46 +3741,73 @@ describe 'Submissions API', type: :request do
expect(json['grade']).to eq grade
end
it "allows posting a rubric assessment" do
student = user_factory(active_all: true)
course_with_teacher(:active_all => true)
@course.enroll_student(student).accept!
a1 = @course.assignments.create!(:title => 'assignment1', :grading_type => 'points', :points_possible => 12)
rubric = rubric_model(:user => @user, :context => @course,
:data => larger_rubric_data)
a1.create_rubric_association(:rubric => rubric, :purpose => 'grading', :use_for_grading => true, :context => @course)
context "posting rubric assessments" do
before(:once) do
@student = user_factory(active_all: true)
course_with_teacher(:active_all => true)
@course.enroll_student(@student).accept!
@a1 = @course.assignments.create!(:title => 'assignment1', :grading_type => 'points', :points_possible => 12)
rubric = rubric_model(:user => @user, :context => @course, :data => larger_rubric_data)
@rubric_association = @a1.create_rubric_association(
:rubric => rubric,
:purpose => 'grading',
:use_for_grading => true,
:context => @course
)
end
api_call(:put,
"/api/v1/courses/#{@course.id}/assignments/#{a1.id}/submissions/#{student.id}.json",
{ :controller => 'submissions_api', :action => 'update',
:format => 'json', :course_id => @course.id.to_s,
:assignment_id => a1.id.to_s, :user_id => student.id.to_s },
{ :rubric_assessment =>
{ :crit1 => { :points => 7 },
:crit2 => { :points => 2, :comments => 'Rock on' } } })
it "allows posting a rubric assessment" do
api_call(
:put,
"/api/v1/courses/#{@course.id}/assignments/#{@a1.id}/submissions/#{@student.id}.json",
{ :controller => 'submissions_api', :action => 'update',
:format => 'json', :course_id => @course.id.to_s,
:assignment_id => @a1.id.to_s, :user_id => @student.id.to_s },
{ :rubric_assessment =>
{ :crit1 => { :points => 7 },
:crit2 => { :points => 2, :comments => 'Rock on' } } }
)
expect(Submission.count).to eq 1
@submission = Submission.first
expect(@submission.user_id).to eq student.id
expect(@submission.score).to eq 9
expect(@submission.rubric_assessment).not_to be_nil
expect(@submission.rubric_assessment.data).to eq(
[{:description=>"B",
:criterion_id=>"crit1",
:comments_enabled=>true,
:points=>7,
:learning_outcome_id=>nil,
:id=>"rat2",
:comments=>nil},
{:description=>"Pass",
:criterion_id=>"crit2",
:comments_enabled=>true,
:points=>2,
:learning_outcome_id=>nil,
:id=>"rat1",
:comments=>"Rock on",
:comments_html=>"Rock on"}]
)
expect(Submission.count).to eq 1
@submission = Submission.first
expect(@submission.user_id).to eq @student.id
expect(@submission.score).to eq 9
expect(@submission.rubric_assessment).not_to be_nil
expect(@submission.rubric_assessment.data).to eq(
[{:description=>"B",
:criterion_id=>"crit1",
:comments_enabled=>true,
:points=>7,
:learning_outcome_id=>nil,
:id=>"rat2",
:comments=>nil},
{:description=>"Pass",
:criterion_id=>"crit2",
:comments_enabled=>true,
:points=>2,
:learning_outcome_id=>nil,
:id=>"rat1",
:comments=>"Rock on",
:comments_html=>"Rock on"}]
)
end
it "does not allow posting a rubric assessment when the rubric association is soft-deleted" do
@rubric_association.destroy
api_call(
:put,
"/api/v1/courses/#{@course.id}/assignments/#{@a1.id}/submissions/#{@student.id}.json",
{ :controller => 'submissions_api', :action => 'update',
:format => 'json', :course_id => @course.id.to_s,
:assignment_id => @a1.id.to_s, :user_id => @student.id.to_s },
{ :rubric_assessment =>
{ :crit1 => { :points => 7 },
:crit2 => { :points => 2, :comments => 'Rock on' } } }
)
@submission = Submission.first
expect(@submission.rubric_assessment).to be_nil
end
end
it "validates the rubric assessment" do

View File

@ -439,6 +439,39 @@ describe ContextController do
expect(response).to be_successful
expect(assigns[:deleted_items]).to include(g1)
end
describe 'Rubric Associations' do
before(:once) do
assignment = assignment_model(course: @course)
rubric = rubric_model({
context: @course,
title: 'Test Rubric',
data: [{
description: 'Some criterion',
points: 10,
id: 'crit1',
ignore_for_scoring: true,
ratings: [
{ description: 'Good', points: 10, id: 'rat1', criterion_id: 'crit1' }
]
}]
})
@association = rubric.associate_with(assignment, @course, purpose: 'grading')
end
it 'shows deleted rubric associations' do
@association.destroy
user_session(@teacher)
get :undelete_index, params: { course_id: @course.id }
expect(assigns[:deleted_items]).to include @association
end
it 'does not show active rubric associations' do
user_session(@teacher)
get :undelete_index, params: { course_id: @course.id }
expect(assigns[:deleted_items]).not_to include @association
end
end
end
describe "POST 'undelete_item'" do
@ -500,6 +533,30 @@ describe ContextController do
post :undelete_item, params: { course_id: @course.id, asset_string: @attachment.asset_string }
expect(@attachment.reload).not_to be_deleted
end
it 'allows undeleting rubric associations' do
assignment = assignment_model(course: @course)
rubric = rubric_model({
context: @course,
title: 'Test Rubric',
data: [{
description: 'Some criterion',
points: 10,
id: 'crit1',
ignore_for_scoring: true,
ratings: [
{ description: 'Good', points: 10, id: 'rat1', criterion_id: 'crit1' }
]
}]
})
association = rubric.associate_with(assignment, @course, purpose: 'grading')
puts "association id is: #{association.id}"
association.destroy
user_session(@teacher)
post :undelete_item, params: { course_id: @course.id, asset_string: association.asset_string }
expect(association.reload).not_to be_deleted
end
end
describe "GET 'roster_user_usage'" do

View File

@ -279,16 +279,18 @@ describe RubricAssociationsController do
delete 'destroy', params: {:course_id => @course.id, :id => @rubric_association.id}
assert_unauthorized
end
it "should delete the rubric if deletable" do
course_with_teacher_logged_in(:active_all => true)
rubric_association_model(:user => @user, :context => @course)
delete 'destroy', params: {:course_id => @course.id, :id => @rubric_association.id}
expect(response).to be_successful
expect(assigns[:association]).not_to be_nil
expect(assigns[:association]).to be_frozen
expect(assigns[:association]).to be_deleted
expect(assigns[:rubric]).not_to be_nil
expect(assigns[:rubric]).to be_deleted
end
it "should_not delete the rubric if still created at the context level instead of the assignment level" do
course_with_teacher_logged_in(:active_all => true)
rubric_association_model(:user => @user, :context => @course)
@ -299,8 +301,9 @@ describe RubricAssociationsController do
expect(assigns[:rubric]).not_to be_deleted
expect(assigns[:rubric]).not_to be_frozen
expect(assigns[:association]).not_to be_nil
expect(assigns[:association]).to be_frozen
expect(assigns[:association]).to be_deleted
end
it "should delete only the association if the rubric is not deletable" do
rubric_association_model
course_with_teacher_logged_in(:active_all => true)
@ -313,7 +316,7 @@ describe RubricAssociationsController do
expect(assigns[:rubric]).not_to be_deleted
expect(assigns[:rubric]).not_to be_frozen
expect(assigns[:association]).not_to be_nil
expect(assigns[:association]).to be_frozen
expect(assigns[:association]).to be_deleted
end
it "should remove aligments links" do

View File

@ -632,6 +632,7 @@ describe RubricsController do
delete 'destroy', params: {:course_id => @course.id, :id => @rubric.id}
assert_unauthorized
end
it "should delete the rubric" do
course_with_teacher_logged_in(:active_all => true)
rubric_association_model(:user => @user, :context => @course)
@ -639,6 +640,17 @@ describe RubricsController do
expect(response).to be_successful
expect(assigns[:rubric]).to be_deleted
end
# This should probably be fixed, but I want to document how this currently behaves.
it "returns a 500 if the rubric cannot be found" do
course_with_teacher_logged_in(active_all: true)
association = rubric_association_model(user: @user, context: @course)
association.destroy
delete 'destroy', params: { course_id: @course.id, id: @rubric.id }
expect(response.status).to eq 500
end
it "should delete the rubric if the rubric is only associated with a course" do
course_with_teacher_logged_in :active_all => true
Account.site_admin.account_users.create!(user: @user)
@ -654,6 +666,7 @@ describe RubricsController do
@rubric.reload
expect(@rubric.deleted?).to be_truthy
end
it "should delete the rubric association even if the rubric doesn't belong to a course" do
course_with_teacher_logged_in :active_all => true
Account.site_admin.account_users.create!(user: @user)

View File

@ -166,4 +166,27 @@ describe AssessmentRequest do
expect(@ignore.reload).to eq @ignore
end
end
describe "#active_rubric_association?" do
before(:once) do
rubric_model
@association = @rubric.associate_with(@assignment, @course, purpose: 'grading', use_for_grading: true)
@request.rubric_association = @association
@request.save!
end
it "returns false if there is no rubric association" do
@request.update!(rubric_association: nil)
expect(@request).not_to be_active_rubric_association
end
it "returns false if the rubric association is soft-deleted" do
@association.destroy
expect(@request).not_to be_active_rubric_association
end
it "returns true if the rubric association exists and is active" do
expect(@request).to be_active_rubric_association
end
end
end

View File

@ -4519,6 +4519,50 @@ describe Assignment do
hash = @assignment.as_json
expect(hash["assignment"]["group_category"]).to eq "Something"
end
context "when including rubric_association" do
before(:once) do
@rubric = Rubric.create!(user: @teacher, context: @course)
end
context "when including root" do
let(:json) { @assignment.as_json(include: [:rubric_association])[:assignment] }
it "does not include a rubric_association when there is no rubric_association" do
expect(json).not_to have_key "rubric_association"
end
it "does not include a rubric_association when there is a rubric_association but it is soft-deleted" do
rubric_association = @rubric.associate_with(@assignment, @course, purpose: 'grading')
rubric_association.destroy
expect(json).not_to have_key "rubric_association"
end
it "includes a rubric_association when there is a rubric_association and it is not deleted" do
rubric_association = @rubric.associate_with(@assignment, @course, purpose: 'grading')
expect(json.dig("rubric_association", "rubric_association", "id")).to eq rubric_association.id
end
end
context "when excluding root" do
let(:json) { @assignment.as_json(include: [:rubric_association], include_root: false) }
it "does not include a rubric_association when there is no rubric_association" do
expect(json).not_to have_key "rubric_association"
end
it "does not include a rubric_association when there is a rubric_association but it is soft-deleted" do
rubric_association = @rubric.associate_with(@assignment, @course, purpose: 'grading')
rubric_association.destroy
expect(json).not_to have_key "rubric_association"
end
it "includes a rubric_association when there is a rubric_association and it is not deleted" do
rubric_association = @rubric.associate_with(@assignment, @course, purpose: 'grading')
expect(json.dig("rubric_association", "id")).to eq rubric_association.id
end
end
end
end
context "ical" do
@ -9396,6 +9440,37 @@ describe Assignment do
end
end
describe "active_rubric_association?" do
before(:once) do
@assignment = @course.assignments.create!(assignment_valid_attributes)
rubric = @course.rubrics.create! { |r| r.user = @teacher }
rubric_association_params = HashWithIndifferentAccess.new({
hide_score_total: "0",
purpose: "grading",
skip_updating_points_possible: false,
update_if_existing: true,
use_for_grading: "1",
association_object: @assignment
})
@association = RubricAssociation.generate(@teacher, rubric, @course, rubric_association_params)
@assignment.update!(rubric_association: @association)
end
it "returns false if there is no rubric association" do
@association.destroy_permanently!
expect(@assignment.reload).not_to be_active_rubric_association
end
it "returns false if the rubric association is soft-deleted" do
@association.destroy
expect(@assignment.reload).not_to be_active_rubric_association
end
it "returns true if the rubric association exists and is active" do
expect(@assignment).to be_active_rubric_association
end
end
def setup_assignment_with_group
assignment_model(:group_category => "Study Groups", :course => @course)
@group = @a.context.groups.create!(:name => "Study Group 1", :group_category => @a.group_category)

View File

@ -286,6 +286,16 @@ describe Context do
}])
end
it 'excludes rubrics associated via soft-deleted rubric associations' do
c1 = Course.create!(:name => 'c1')
r = Rubric.create!(context: c1, title: 'testing')
user = user_factory(:active_all => true)
association = RubricAssociation.create!(context: c1, rubric: r, purpose: :bookmark, association_object: c1)
association.destroy
c1.enroll_user(user, "TeacherEnrollment", :enrollment_state => "active")
expect(c1.rubric_contexts(user)).to be_empty
end
it 'returns contexts in alphabetically sorted order' do
great_grandparent = Account.default
grandparent = Account.create!(name: 'AAA', parent_account: great_grandparent)

View File

@ -384,6 +384,30 @@ describe ModeratedGrading::ProvisionalGrade do
expect(real_assessment.data).to eq provisional_assessment.data
end
it "does not publish rubric assessments when the rubric association is soft-deleted" do
outcome_with_rubric(course: course)
association = @rubric.associate_with(assignment, course, purpose: 'grading', use_for_grading: true)
association.destroy
submission = assignment.submit_homework(student, submission_type: 'online_text_entry', body: 'hallo')
provisional_grade = submission.find_or_create_provisional_grade!(scorer, score: 1)
association.assess(
user: student,
assessor: scorer,
artifact: provisional_grade,
assessment: {
assessment_type: 'grading',
:"criterion_#{@rubric.criteria_object.first.id}" => {
points: 3,
comments: "good 4 u"
}
}
)
expect(submission.rubric_assessments.first).to be_nil
end
it "does not error when a rubric has been deleted after an assessment took place" do
outcome_with_rubric(course: course)
association = @rubric.associate_with(assignment, course, purpose: 'grading', use_for_grading: true)

View File

@ -34,6 +34,37 @@ describe RubricAssessment do
@association = @rubric.associate_with(@assignment, @course, :purpose => 'grading', :use_for_grading => true)
end
describe "active_rubric_association?" do
before(:once) do
@assessment = @association.assess({
:user => @student,
:assessor => @teacher,
:artifact => @assignment.find_or_create_submission(@student),
:assessment => {
:assessment_type => 'grading',
:criterion_crit1 => {
:points => 5,
:comments => "comments",
}
}
})
end
it "returns false if there is no rubric association" do
@assessment.update!(rubric_association: nil)
expect(@assessment).not_to be_active_rubric_association
end
it "returns false if the rubric association is soft-deleted" do
@association.destroy
expect(@assessment).not_to be_active_rubric_association
end
it "returns true if the rubric association exists and is active" do
expect(@assessment).to be_active_rubric_association
end
end
it { is_expected.to have_many(:learning_outcome_results).dependent(:destroy) }
it "should htmlify the rating comments" do

View File

@ -442,7 +442,30 @@ describe RubricAssociation do
end
describe "workflow_state" do
it "is set to active by default" do
before(:once) do
@course = Course.create!
@rubric = @course.rubrics.create!
@association = RubricAssociation.create!(
rubric: @rubric,
association_object: @course,
context: @course,
purpose: "bookmark"
)
end
it "is set to 'active' by default" do
expect(@association).to be_active
end
it "gets set to 'deleted' when soft-deleted" do
expect { @association.destroy }.to change {
@association.workflow_state
}.from("active").to("deleted")
end
end
describe "#restore" do
it "sets the workflow_state to 'active'" do
course = Course.create!
rubric = course.rubrics.create!
association = RubricAssociation.create!(
@ -451,8 +474,8 @@ describe RubricAssociation do
context: course,
purpose: "bookmark"
)
expect(association).to be_active
association.destroy
expect { association.restore }.to change { association.workflow_state }.from("deleted").to("active")
end
end
end

View File

@ -237,7 +237,7 @@ describe Rubric do
a2 = rubric.associate_with(assignment2, @course, :purpose => 'grading')
assignment2.destroy
expect(RubricAssociation.where(:id => a2).first).to be_nil # association should be destroyed
expect(RubricAssociation.where(:id => a2).first).to be_deleted
rubric.reload
expect(rubric).to be_active

View File

@ -210,6 +210,31 @@ describe SpeedGrader::Assignment do
).to eq comment.provisional_grade_id.to_s
end
context "rubric association" do
before(:once) do
@assignment = assignment_model(course: @course)
end
let(:json) { SpeedGrader::Assignment.new(@assignment, @user).json }
it "does not include rubric_association when one does not exist" do
expect(json).not_to have_key "rubric_association"
end
it "does not include rubric_association when one exists but it is not active" do
rubric = rubric_model
association = rubric.associate_with(@assignment, @course, purpose: "grading", use_for_grading: true)
association.destroy
expect(json).not_to have_key "rubric_association"
end
it "includes a rubric_association when one exists and is active" do
rubric = rubric_model
association = rubric.associate_with(@assignment, @course, purpose: "grading", use_for_grading: true)
expect(json.dig("rubric_association", "id")).to eq association.id.to_s
end
end
context "students and active course sections" do
before(:once) do
@course = course_factory(active_course: true)