vdd backend for quizzes

[Fixes #CNVS-1145]

* Adds support for attaching AssignmentOverrides to quizzes
* Links common overrides for Assignments that belong to Quizzes
  so quiz.due_dates_for(user) == quiz.assignment.due_dates_for(user)
* Moves common functionality for VDD out of the Assignment model
  and into the DatesOverridable module, which is now included on
  Quiz for a consistent interface

Test plan:
* Run through a few of the old VDD scenarios and verify things
  still work as expected since a lot of code has shifted around
* No interface changes have been made to quizzes yet, so nothing
  to check there

  https://gist.github.com/f12b3694016f3dcc979e

Change-Id: I32b4a54273f90fac689e05174039b8efc952dd39
Reviewed-on: https://gerrit.instructure.com/16028
Tested-by: Jenkins <jenkins@instructure.com>
Reviewed-by: Simon Williams <simon@instructure.com>
QA-Review: Simon Williams <simon@instructure.com>
This commit is contained in:
Dave Donahue 2012-12-12 14:14:17 -05:00
parent 61259ca30d
commit 3b3b746cca
14 changed files with 1002 additions and 568 deletions

View File

@ -25,6 +25,7 @@ class Assignment < ActiveRecord::Base
include CopyAuthorizedLinks
include Mutable
include ContextModuleItem
include DatesOverridable
attr_accessible :title, :name, :description, :due_at, :points_possible,
:min_score, :max_score, :mastery_score, :grading_type, :submission_types,
@ -34,7 +35,7 @@ class Assignment < ActiveRecord::Base
:notify_of_update, :time_zone_edited, :turnitin_enabled, :turnitin_settings,
:set_custom_field_values, :context, :position, :allowed_extensions,
:external_tool_tag_attributes, :freeze_on_copy
attr_accessor :original_id, :updating_user, :copying, :applied_overrides
attr_accessor :original_id, :updating_user, :copying
has_many :submissions, :class_name => 'Submission', :dependent => :destroy
has_many :attachments, :as => :context, :dependent => :destroy
@ -49,12 +50,10 @@ class Assignment < ActiveRecord::Base
belongs_to :cloned_item
belongs_to :grading_standard
belongs_to :group_category
has_many :assignment_overrides, :dependent => :destroy
has_many :active_assignment_overrides, :class_name => 'AssignmentOverride', :conditions => {:workflow_state => 'active'}
has_one :external_tool_tag, :class_name => 'ContentTag', :as => :context, :dependent => :destroy
validates_associated :external_tool_tag, :if => :external_tool?
validates_associated :assignment_overrides
accepts_nested_attributes_for :external_tool_tag, :reject_if => proc { |attrs|
# only accept the url and new_tab params, the other accessible
# params don't apply to an content tag being used as an external_tool_tag
@ -143,99 +142,10 @@ class Assignment < ActiveRecord::Base
if group_category_id_changed?
# needs to be .each(&:destroy) instead of .update_all(:workflow_state =>
# 'deleted') so that the override gets versioned properly
assignment_overrides.active.scoped(:conditions => {:set_type => 'Group'}).each(&:destroy)
active_assignment_overrides.scoped(:conditions => {:set_type => 'Group'}).each(&:destroy)
end
end
def overridden_for(user)
AssignmentOverrideApplicator.assignment_overridden_for(self, user)
end
def overrides_visible_to(user, overrides=active_assignment_overrides)
# the visible_to scope is potentially expensive. skip its conditions if the
# initial scope is already empty
if overrides.first.present?
overrides.visible_to(user, context)
else
overrides
end
end
def has_overrides?
assignment_overrides.count > 0
end
# returns two values indicating which due dates for this assignment apply
# and/or are visible to the user.
#
# the first is the due date as it applies to the user as a student, if any
# (nil if the user has no student enrollment(s) in the assignment's course)
#
# the second is a list of due dates a they apply to users, sections, or
# groups visible to the user as an admin (nil if the user has no
# admin/observer enrollment(s) in the assignment's course)
#
# in both cases, "due dates" is a hash with due_at (full timestamp), all_day
# flag, and all_day_date. for the "as an admin" list, each due date from
# an override will also have a 'title' key to identify which subset of the
# course is affected by that due date, and an 'override' key referencing the
# override itself. for the original due date, it will instead have a 'base'
# flag (value true).
def due_dates_for(user)
as_student, as_admin = nil, nil
return nil, nil if context.nil?
if context.user_has_been_student?(user)
as_student = self.overridden_for(user).due_date_hash
end
if context.user_has_been_admin?(user)
as_admin = due_dates_visible_to(user)
elsif context.user_has_been_observer?(user)
as_admin = observed_student_due_dates(user).uniq
if as_admin.empty?
as_admin = [self.overridden_for(user).due_date_hash]
end
elsif context.user_has_no_enrollments?(user)
as_admin = all_due_dates
end
return as_student, as_admin
end
def all_due_dates
all_dates = assignment_overrides.overriding_due_at.map(&:as_hash)
all_dates << due_date_hash.merge(:base => true)
end
def due_dates_visible_to(user)
# Overrides
overrides = overrides_visible_to(user).overriding_due_at
list = overrides.map(&:as_hash)
# Base
list << self.due_date_hash.merge(:base => true)
end
def observed_student_due_dates(user)
ObserverEnrollment.observed_students(context, user).map do |student, enrollments|
self.overridden_for(student).due_date_hash
end
end
def due_date_hash
hash = { :due_at => due_at, :all_day => all_day, :all_day_date => all_day_date }
if @applied_overrides && override = @applied_overrides.find { |o| o.due_at == due_at }
hash[:override] = override
hash[:title] = override.title
end
hash
end
def self.due_date_compare_value(date)
# due dates are considered equal if they're the same up to the minute
date.to_i / 60
@ -245,72 +155,6 @@ class Assignment < ActiveRecord::Base
due_date_compare_value(date1) == due_date_compare_value(date2)
end
def multiple_due_dates_apply_to(user)
as_instructor = self.due_dates_for(user).second
as_instructor && as_instructor.map{ |hash|
Assignment.due_date_compare_value(hash[:due_at]) }.uniq.size > 1
end
# like due_dates_for, but for unlock_at values instead. for consistency, each
# unlock_at is still represented by a hash, even though the "as a student"
# value will only have one key.
def unlock_ats_for(user)
as_student, as_instructor = nil, nil
if context.user_has_been_student?(user)
overridden = self.overridden_for(user)
as_student = { :unlock_at => overridden.unlock_at }
end
if context.user_has_been_instructor?(user)
overrides = self.overrides_visible_to(user).overriding_unlock_at
as_instructor = overrides.map do |override|
{
:title => override.title,
:unlock_at => override.unlock_at,
:override => override
}
end
as_instructor << {
:base => true,
:unlock_at => self.unlock_at
}
end
return as_student, as_instructor
end
# like unlock_ats_for, but for lock_at values instead.
def lock_ats_for(user)
as_student, as_instructor = nil, nil
if context.user_has_been_student?(user)
overridden = self.overridden_for(user)
as_student = { :lock_at => overridden.lock_at }
end
if context.user_has_been_instructor?(user)
overrides = self.overrides_visible_to(user).overriding_lock_at
as_instructor = overrides.map do |override|
{
:title => override.title,
:lock_at => override.lock_at,
:override => override
}
end
as_instructor << {
:base => true,
:lock_at => self.lock_at
}
end
return as_student, as_instructor
end
def schedule_do_auto_peer_review_job_if_automatic_peer_review
if peer_reviews && automatic_peer_reviews && !peer_reviews_assigned
# handle if it has already come due, but has not yet been auto_peer_reviewed

View File

@ -25,10 +25,12 @@ class AssignmentOverride < ActiveRecord::Base
attr_accessible
belongs_to :assignment
belongs_to :quiz
belongs_to :set, :polymorphic => true
has_many :assignment_override_students, :dependent => :destroy
validates_presence_of :assignment, :assignment_version, :title
validates_presence_of :assignment_version, :if => :assignment
validates_presence_of :title
validates_inclusion_of :set_type, :in => %w(CourseSection Group ADHOC)
validates_length_of :title, :maximum => maximum_string_length, :allow_nil => true
@ -53,6 +55,12 @@ class AssignmentOverride < ActiveRecord::Base
end
end
validate do |record|
if [record.assignment, record.quiz].all?(&:nil?)
record.errors.add :base, "assignment or quiz required"
end
end
workflow do
state :active
state :deleted
@ -72,7 +80,17 @@ class AssignmentOverride < ActiveRecord::Base
before_validation :default_values
def default_values
self.set_type ||= 'ADHOC'
self.assignment_version = assignment.version_number if assignment
if assignment
self.assignment_version = assignment.version_number
self.quiz = assignment.quiz
self.quiz_version = quiz.version_number if quiz
elsif quiz
self.quiz_version = quiz.version_number
self.assignment = quiz.assignment
self.assignment_version = assignment.version_number if assignment
end
self.title = set.name if set_type != 'ADHOC' && set
end
protected :default_values

View File

@ -20,11 +20,12 @@ class AssignmentOverrideStudent < ActiveRecord::Base
belongs_to :assignment
belongs_to :assignment_override
belongs_to :user
belongs_to :quiz
attr_accessible :user
validates_presence_of :assignment, :assignment_override, :user
validates_uniqueness_of :user_id, :scope => :assignment_id
validates_presence_of :assignment_override, :user
validates_uniqueness_of :user_id, :scope => [:assignment_id, :quiz_id]
validate :assignment_override do |record|
if record.assignment_override && record.assignment_override.set_type != 'ADHOC'
@ -39,14 +40,31 @@ class AssignmentOverrideStudent < ActiveRecord::Base
end
validate :user do |record|
if record.user && record.assignment && record.user.student_enrollments.scoped(:conditions => {:course_id => record.assignment.context_id}).first.nil?
if record.user && record.context_id && record.user.student_enrollments.scoped(:conditions => {:course_id => record.context_id}).first.nil?
record.errors.add :user, "is not in the assignment's course"
end
end
validate do |record|
if [record.assignment, record.quiz].all?(&:nil?)
record.errors.add :base, "requires assignment or quiz"
end
end
def context_id
if quiz
quiz.context_id
elsif assignment
assignment.context_id
end
end
before_validation :default_values
def default_values
self.assignment_id = self.assignment_override.assignment_id if self.assignment_override
if assignment_override
self.assignment_id = assignment_override.assignment_id
self.quiz_id = assignment_override.quiz_id
end
end
protected :default_values
end

View File

@ -25,6 +25,7 @@ class Quiz < ActiveRecord::Base
include ActionView::Helpers::SanitizeHelper
extend ActionView::Helpers::SanitizeHelper::ClassMethods
include ContextModuleItem
include DatesOverridable
attr_accessible :title, :description, :points_possible, :assignment_id, :shuffle_answers,
:show_correct_answers, :time_limit, :allowed_attempts, :scoring_policy, :quiz_type,
@ -53,6 +54,7 @@ class Quiz < ActiveRecord::Base
before_save :set_defaults
after_save :update_assignment
after_save :touch_context
after_save :link_assignment_overrides, :if => :new_assignment_id?
serialize :quiz_data
@ -89,6 +91,19 @@ class Quiz < ActiveRecord::Base
@stored_questions = nil
end
protected :set_defaults
def new_assignment_id?
last_assignment_id != assignment_id
end
def link_assignment_overrides
collections = [assignment_overrides, assignment_override_students]
collections += [assignment.assignment_overrides, assignment.assignment_override_students] if assignment
collections.each do |collection|
collection.update_all({ :assignment_id => assignment_id, :quiz_id => id })
end
end
def build_assignment
if self.available? && !self.assignment_id && self.graded? && @saved_by != :assignment && @saved_by != :clone

View File

@ -0,0 +1,20 @@
class AddQuizIdToAssignmentOverrides < ActiveRecord::Migration
tag :predeploy
def self.up
add_column :assignment_overrides, :quiz_id, :integer, :limit => 8
add_column :assignment_overrides, :quiz_version, :integer
add_index :assignment_overrides, :quiz_id
change_column :assignment_overrides, :assignment_id, :integer, :limit => 8, :null => true
change_column :assignment_overrides, :assignment_version, :integer, :null => true
end
def self.down
remove_index :assignment_overrides, :quiz_id
remove_column :assignment_overrides, :quiz_id, :quiz_version
change_column :assignment_overrides, :assignment_id, :integer, :limit => 8, :null => false
change_column :assignment_overrides, :assignment_version, :integer, :null => false
end
end

View File

@ -0,0 +1,15 @@
class AddQuizIdToAssignmentOverrideStudents < ActiveRecord::Migration
tag :predeploy
def self.up
add_column :assignment_override_students, :quiz_id, :integer, :limit => 8
add_index :assignment_override_students, :quiz_id
change_column :assignment_override_students, :assignment_id, :integer, :limit => 8, :null => true
end
def self.down
remove_column :assignment_override_students, :quiz_id
remove_index :assignment_override_students, :quiz_id
change_column :assignment_override_students, :assignment_id, :integer, :limit => 8, :null => false
end
end

View File

@ -17,39 +17,45 @@
#
module AssignmentOverrideApplicator
# top-level method intended for consumption. given an assignment (of specific
# top-level method intended for consumption. given an assignment or quiz(of specific
# version) and user, determine the list of overrides, apply them to the
# assignment, and return the overridden stand-in.
def self.assignment_overridden_for(assignment, user)
overrides = self.overrides_for_assignment_and_user(assignment, user)
# assignment or quiz, and return the overridden stand-in.
def self.assignment_overridden_for(assignment_or_quiz, user)
overrides = self.overrides_for_assignment_and_user(assignment_or_quiz, user)
if overrides.empty?
assignment
assignment_or_quiz
else
self.assignment_with_overrides(assignment, overrides)
self.assignment_with_overrides(assignment_or_quiz, overrides)
end
end
def self.quiz_overridden_for(quiz, user)
assignment_overridden_for(quiz, user)
end
# determine list of overrides (of appropriate version) that apply to the
# assignment (of specific version) for a particular user. the overrides are
# assignment or quiz(of specific version) for a particular user. the overrides are
# returned in priority order; the first override to contain an overridden
# value for a particular field is used for that field
def self.overrides_for_assignment_and_user(assignment, user)
Rails.cache.fetch([user, assignment, assignment.version_number, 'overrides'].cache_key) do
def self.overrides_for_assignment_and_user(assignment_or_quiz, user)
Rails.cache.fetch([user, assignment_or_quiz, assignment_or_quiz.version_number, 'overrides'].cache_key) do
# return an empty array to the block if there is nothing to do here
next [] unless assignment.has_overrides?
next [] unless assignment_or_quiz.has_overrides?
overrides = []
# get list of overrides that might apply. adhoc override is highest
# priority, then group override, then section overrides by position. DO
# NOT exclude deleted overrides, yet
adhoc_membership = AssignmentOverrideStudent.scoped(:conditions => {:assignment_id => assignment.id, :user_id => user.id}).first
key = assignment_or_quiz.is_a?(Quiz) ? :quiz_id : :assignment_id
adhoc_membership = AssignmentOverrideStudent.scoped(:conditions => {key => assignment_or_quiz.id, :user_id => user.id}).first
overrides << adhoc_membership.assignment_override if adhoc_membership
if assignment.group_category && group = user.current_groups.scoped(:conditions => {:group_category_id => assignment.group_category_id}).first
group_override = assignment.assignment_overrides.
if assignment_or_quiz.is_a?(Assignment) && assignment_or_quiz.group_category && group = user.current_groups.scoped(:conditions => {:group_category_id => assignment_or_quiz.group_category_id}).first
group_override = assignment_or_quiz.assignment_overrides.
scoped(:conditions => {:set_type => 'Group', :set_id => group.id}).
first
overrides << group_override if group_override
@ -57,20 +63,21 @@ module AssignmentOverrideApplicator
section_ids = user.enrollments.active.scoped(:conditions =>
{ :type => ['StudentEnrollment', 'ObserverEnrollment'],
:course_id => assignment.context_id}).map(&:course_section_id)
:course_id => assignment_or_quiz.context_id}).map(&:course_section_id)
section_overrides = assignment.assignment_overrides.
section_overrides = assignment_or_quiz.assignment_overrides.
scoped(:conditions => {:set_type => 'CourseSection', :set_id => section_ids})
# TODO add position column to assignment_override, nil for non-section
# overrides, (assignment, position) unique for section overrides
# overrides, (assignment_or_quiz, position) unique for section overrides
overrides += section_overrides#.scoped(:order => :position)
# for each potential override discovered, make sure we look at the
# appropriate version
overrides = overrides.map do |override|
override_version = override.versions.detect do |version|
version.model.assignment_version <= assignment.version_number
model_version = assignment_or_quiz.is_a?(Quiz) ? version.model.quiz_version : version.model.assignment_version
model_version <= assignment_or_quiz.version_number
end
override_version ? override_version.model : nil
end
@ -82,47 +89,53 @@ module AssignmentOverrideApplicator
end
# apply the overrides calculated by collapsed_overrides to a clone of the
# assignment which can then be used in place of the assignment. the clone is
# marked readonly to prevent saving
def self.assignment_with_overrides(assignment, overrides)
# assignment or quiz which can then be used in place of the original object.
# the clone is marked readonly to prevent saving
def self.assignment_with_overrides(assignment_or_quiz, overrides)
# ActiveRecord::Base#clone nils out the primary key; put it back
cloned_assignment = assignment.clone
cloned_assignment.id = assignment.id
cloned_assignment_or_quiz = assignment_or_quiz.clone
cloned_assignment_or_quiz.id = assignment_or_quiz.id
# update attributes with overrides
self.collapsed_overrides(assignment, overrides).each do |field,value|
self.collapsed_overrides(assignment_or_quiz, overrides).each do |field,value|
# for any times in the value set, bring them back from raw UTC into the
# current Time.zone before placing them in the assignment
value = value.in_time_zone if value && value.respond_to?(:in_time_zone) && !value.is_a?(Date)
cloned_assignment.write_attribute(field, value)
cloned_assignment_or_quiz.write_attribute(field, value)
end
cloned_assignment.applied_overrides = overrides
cloned_assignment.readonly!
cloned_assignment_or_quiz.applied_overrides = overrides
cloned_assignment_or_quiz.readonly!
# make new_record? match the original (typically always true on AR clones,
# at least until saved, which we don't want to do)
klass = class << cloned_assignment; self; end
klass.send(:define_method, :new_record?) { assignment.new_record? }
klass = class << cloned_assignment_or_quiz; self; end
klass.send(:define_method, :new_record?) { assignment_or_quiz.new_record? }
cloned_assignment
cloned_assignment_or_quiz
end
# given an assignment (of specific version) and an ordered list of overrides
def self.quiz_with_overrides(quiz, overrides)
assignment_with_overrides(quiz, overrides)
end
# given an assignment or quiz (of specific version) and an ordered list of overrides
# (see overrides_for_assignment_and_user), return a hash of values for each
# overrideable field. for caching, the same set of overrides should produce
# the same collapsed assignment, regardless of the user that ended up at that
# the same collapsed assignment or quiz, regardless of the user that ended up at that
# set of overrides.
def self.collapsed_overrides(assignment, overrides)
Rails.cache.fetch([assignment, assignment.version_number, self.overrides_hash(overrides)].cache_key) do
def self.collapsed_overrides(assignment_or_quiz, overrides)
Rails.cache.fetch([assignment_or_quiz, assignment_or_quiz.version_number, self.overrides_hash(overrides)].cache_key) do
overridden_data = {}
# clone the assignment, apply overrides, and freeze
# clone the assignment_or_quiz, apply overrides, and freeze
[:due_at, :all_day, :all_day_date, :unlock_at, :lock_at].each do |field|
value = self.send("overridden_#{field}", assignment, overrides)
# force times to un-zoned UTC -- this will be a cached value and should
# not care about the TZ of the user that cached it. the user's TZ will
# be applied before it's returned.
value = value.utc if value && value.respond_to?(:utc) && !value.is_a?(Date)
overridden_data[field] = value
if assignment_or_quiz.respond_to?(field)
value = self.send("overridden_#{field}", assignment_or_quiz, overrides)
# force times to un-zoned UTC -- this will be a cached value and should
# not care about the TZ of the user that cached it. the user's TZ will
# be applied before it's returned.
value = value.utc if value && value.respond_to?(:utc) && !value.is_a?(Date)
overridden_data[field] = value
end
end
overridden_data
end
@ -135,24 +148,24 @@ module AssignmentOverrideApplicator
end
# perform overrides of specific fields
def self.override_for_due_at(assignment, overrides)
def self.override_for_due_at(assignment_or_quiz, overrides)
applicable_overrides = overrides.select(&:due_at_overridden)
if applicable_overrides.empty?
assignment
assignment_or_quiz
elsif override = applicable_overrides.detect{ |o| o.due_at.nil? }
override
else
override = applicable_overrides.sort_by(&:due_at).last
if assignment.due_at && assignment.due_at > override.due_at
assignment
if assignment_or_quiz.due_at && assignment_or_quiz.due_at > override.due_at
assignment_or_quiz
else
override
end
end
end
def self.overridden_due_at(assignment, overrides)
override_for_due_at(assignment, overrides).due_at
def self.overridden_due_at(assignment_or_quiz, overrides)
override_for_due_at(assignment_or_quiz, overrides).due_at
end
def self.overridden_all_day(assignment, overrides)
@ -163,13 +176,13 @@ module AssignmentOverrideApplicator
override_for_due_at(assignment, overrides).all_day_date
end
def self.overridden_unlock_at(assignment, overrides)
def self.overridden_unlock_at(assignment_or_quiz, overrides)
unlock_ats = overrides.select(&:unlock_at_overridden).map(&:unlock_at)
unlock_ats.any?(&:nil?) ? nil : [assignment.unlock_at, *unlock_ats].compact.min
unlock_ats.any?(&:nil?) ? nil : [assignment_or_quiz.unlock_at, *unlock_ats].compact.min
end
def self.overridden_lock_at(assignment, overrides)
def self.overridden_lock_at(assignment_or_quiz, overrides)
lock_ats = overrides.select(&:lock_at_overridden).map(&:lock_at)
lock_ats.any?(&:nil?) ? nil : [assignment.lock_at, *lock_ats].compact.max
lock_ats.any?(&:nil?) ? nil : [assignment_or_quiz.lock_at, *lock_ats].compact.max
end
end

171
lib/dates_overridable.rb Normal file
View File

@ -0,0 +1,171 @@
module DatesOverridable
attr_accessor :applied_overrides
def self.included(base)
base.has_many :assignment_overrides, :dependent => :destroy
base.has_many :active_assignment_overrides, :class_name => 'AssignmentOverride', :conditions => {:workflow_state => 'active'}
base.has_many :assignment_override_students, :dependent => :destroy
base.validates_associated :assignment_overrides
end
def overridden_for(user)
AssignmentOverrideApplicator.assignment_overridden_for(self, user)
end
def overrides_visible_to(user, overrides=active_assignment_overrides)
# the visible_to scope is potentially expensive. skip its conditions if the
# initial scope is already empty
if overrides.first.present?
overrides.visible_to(user, context)
else
overrides
end
end
def has_overrides?
assignment_overrides.count > 0
end
# returns two values indicating which due dates for this assignment apply
# and/or are visible to the user.
#
# the first is the due date as it applies to the user as a student, if any
# (nil if the user has no student enrollment(s) in the assignment's course)
#
# the second is a list of due dates a they apply to users, sections, or
# groups visible to the user as an admin (nil if the user has no
# admin/observer enrollment(s) in the assignment's course)
#
# in both cases, "due dates" is a hash with due_at (full timestamp), all_day
# flag, and all_day_date. for the "as an admin" list, each due date from
# an override will also have a 'title' key to identify which subset of the
# course is affected by that due date, and an 'override' key referencing the
# override itself. for the original due date, it will instead have a 'base'
# flag (value true).
def due_dates_for(user)
as_student, as_admin = nil, nil
return nil, nil if context.nil?
if context.user_has_been_student?(user)
as_student = self.overridden_for(user).due_date_hash
end
if context.user_has_been_admin?(user)
as_admin = due_dates_visible_to(user)
elsif context.user_has_been_observer?(user)
as_admin = observed_student_due_dates(user).uniq
if as_admin.empty?
as_admin = [self.overridden_for(user).due_date_hash]
end
elsif context.user_has_no_enrollments?(user)
as_admin = all_due_dates
end
return as_student, as_admin
end
def all_due_dates
all_dates = assignment_overrides.overriding_due_at.map(&:as_hash)
all_dates << due_date_hash.merge(:base => true)
end
def due_dates_visible_to(user)
# Overrides
overrides = overrides_visible_to(user).overriding_due_at
list = overrides.map(&:as_hash)
# Base
list << self.due_date_hash.merge(:base => true)
end
def observed_student_due_dates(user)
ObserverEnrollment.observed_students(context, user).map do |student, enrollments|
self.overridden_for(student).due_date_hash
end
end
def due_date_hash
hash = { :due_at => due_at }
if self.is_a?(Assignment)
hash.merge!({ :all_day => all_day, :all_day_date => all_day_date })
end
if @applied_overrides && override = @applied_overrides.find { |o| o.due_at == due_at }
hash[:override] = override
hash[:title] = override.title
end
hash
end
def multiple_due_dates_apply_to(user)
as_instructor = self.due_dates_for(user).second
as_instructor && as_instructor.map{ |hash|
self.class.due_date_compare_value(hash[:due_at]) }.uniq.size > 1
end
# like due_dates_for, but for unlock_at values instead. for consistency, each
# unlock_at is still represented by a hash, even though the "as a student"
# value will only have one key.
def unlock_ats_for(user)
as_student, as_instructor = nil, nil
if context.user_has_been_student?(user)
overridden = self.overridden_for(user)
as_student = { :unlock_at => overridden.unlock_at }
end
if context.user_has_been_instructor?(user)
overrides = self.overrides_visible_to(user).overriding_unlock_at
as_instructor = overrides.map do |override|
{
:title => override.title,
:unlock_at => override.unlock_at,
:override => override
}
end
as_instructor << {
:base => true,
:unlock_at => self.unlock_at
}
end
return as_student, as_instructor
end
# like unlock_ats_for, but for lock_at values instead.
def lock_ats_for(user)
as_student, as_instructor = nil, nil
if context.user_has_been_student?(user)
overridden = self.overridden_for(user)
as_student = { :lock_at => overridden.lock_at }
end
if context.user_has_been_instructor?(user)
overrides = self.overrides_visible_to(user).overriding_lock_at
as_instructor = overrides.map do |override|
{
:title => override.title,
:lock_at => override.lock_at,
:override => override
}
end
as_instructor << {
:base => true,
:lock_at => self.lock_at
}
end
return as_student, as_instructor
end
end

View File

@ -17,7 +17,7 @@
#
def assignment_override_model(opts={})
assignment = opts.delete(:assignment) || assignment_model(opts)
assignment = opts.delete(:assignment) || opts.delete(:quiz) || assignment_model(opts)
override_for = opts.delete(:set)
@override = factory_with_protected_attributes(assignment.assignment_overrides, assignment_override_valid_attributes.merge(opts))
@override.due_at_overridden = true if opts[:due_at]

View File

@ -0,0 +1,422 @@
#
# Copyright (C) 2012 Instructure, Inc.
#
# This file is part of Canvas.
#
# Canvas is free software: you can redistribute it and/or modify it under
# the terms of the GNU Affero General Public License as published by the Free
# Software Foundation, version 3 of the License.
#
# Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
# details.
#
# You should have received a copy of the GNU Affero General Public License along
# with this program. If not, see <http://www.gnu.org/licenses/>.
#
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper.rb')
shared_examples_for "an object whose dates are overridable" do
# let(:overridable) - an Assignment or Quiz
# let(:overridable_type) - :assignment or :quiz
let(:course) { overridable.context }
let(:override) { assignment_override_model(overridable_type => overridable) }
describe "overridden_for" do
before do
student_in_course(:course => course)
end
context "when there are overrides" do
before do
override.override_due_at(7.days.from_now)
override.save!
override_student = override.assignment_override_students.build
override_student.user = @student
override_student.save!
end
it "returns a clone of the object with the relevant override(s) applied" do
overridden = overridable.overridden_for(@student)
overridden.due_at.should == override.due_at
end
end
context "with no overrides" do
it "returns the original object" do
@overridden = overridable.overridden_for(@student)
@overridden.due_at.should == overridable.due_at
end
end
end
describe "has_overrides?" do
subject { overridable.has_overrides? }
context "when it does" do
before { override }
it { should be_true }
end
context "when it doesn't" do
it { should be_false }
end
end
describe "#overrides_visible_to(user)" do
before :each do
override.set = course.default_section
override.save!
end
it "delegates to visible_to on the active overrides by default" do
@expected_value = stub("expected value")
overridable.active_assignment_overrides.expects(:visible_to).with(@teacher, course).returns(@expected_value)
overridable.overrides_visible_to(@teacher).should == @expected_value
end
it "allows overriding the scope" do
override.destroy
overridable.overrides_visible_to(@teacher).should be_empty
overridable.overrides_visible_to(@teacher, overridable.assignment_overrides(true)).should == [override]
end
it "skips the visible_to application if the scope is already empty" do
override.destroy
overridable.active_assignment_overrides.expects(:visible_to).times(0)
overridable.overrides_visible_to(@teacher)
end
it "returns a scope" do
# can't use "should respond_to", because that delegates to the instantiated Array
lambda{ overridable.overrides_visible_to(@teacher).scoped({}) }.should_not raise_exception
end
end
describe "#due_dates_for(user)" do
before :each do
course_with_student(:course => course)
override.set = course.default_section
override.override_due_at(2.days.ago)
override.save!
end
context "for a student" do
before do
@as_student, @as_instructor = overridable.due_dates_for(@student)
end
it "does not return instructor dates" do
@as_instructor.should be_nil
end
it "returns a relevant student date" do
@as_student.should_not be_nil
end
end
context "for a teacher" do
before do
@as_student, @as_instructor = overridable.due_dates_for(@teacher)
end
it "does not return a student date" do
@as_student.should be_nil
end
it "returns a list of instructor dates" do
@as_instructor.should_not be_nil
end
end
it "returns both for a user that's both a student and a teacher" do
course_with_ta(:course => course, :user => @student, :active_all => true)
as_student, as_instructor = overridable.due_dates_for(@student)
as_student.should_not be_nil
as_instructor.should_not be_nil
end
it "uses the overridden due date as the applicable due date" do
as_student, _ = overridable.due_dates_for(@student)
as_student[:due_at].should == override.due_at
if overridable.is_a?(Assignment)
as_student[:all_day].should == override.all_day
as_student[:all_day_date].should == override.all_day_date
end
end
it "includes the base due date in the list of due dates" do
_, as_instructor = overridable.due_dates_for(@teacher)
expected_params = { :base => true, :due_at => overridable.due_at }
if overridable.is_a?(Assignment)
expected_params.merge!({
:all_day => overridable.all_day,
:all_day_date => overridable.all_day_date
})
end
as_instructor.should include expected_params
end
it "includes visible due date overrides in the list of due dates" do
_, as_instructor = overridable.due_dates_for(@teacher)
as_instructor.should include({
:title => @course.default_section.name,
:due_at => override.due_at,
:all_day => override.all_day,
:all_day_date => override.all_day_date,
:override => override
})
end
it "excludes visible overrides that don't override due_at from the list of due dates" do
override.clear_due_at_override
override.save!
_, as_instructor = overridable.due_dates_for(@teacher)
as_instructor.size.should == 1
as_instructor.first[:base].should be_true
end
it "excludes overrides that aren't visible from the list of due dates" do
@enrollment = @teacher.enrollments.first
@enrollment.limit_privileges_to_course_section = true
@enrollment.save!
@section2 = course.course_sections.create!
override.set = @section2
override.save!
_, as_instructor = overridable.due_dates_for(@teacher)
as_instructor.size.should == 1
as_instructor.first[:base].should be_true
end
end
describe "due_date_hash" do
it "returns the due at, all day, and all day date params" do
due = 5.days.from_now
a = Assignment.new(:due_at => due)
a.due_date_hash.should == { :due_at => due, :all_day => false, :all_day_date => nil }
end
end
describe "observed_student_due_dates" do
it "returns a list of overridden due date hashes" do
a = Assignment.new
u = User.new
student1, student2 = [mock, mock]
{ student1 => '1', student2 => '2' }.each do |student, value|
a.expects(:overridden_for).with(student).returns \
mock(:due_date_hash => { :student => value })
end
ObserverEnrollment.expects(:observed_students).returns({student1 => [], student2 => []})
override_hashes = a.observed_student_due_dates(u)
override_hashes.should =~ [ { :student => '1' }, { :student => '2' } ]
end
end
describe "#unlock_ats_for(user)" do
before :each do
course_with_student(:course => course, :active_all => true)
overridable.update_attributes(:unlock_at => 2.days.ago)
override.set = course.default_section
override.override_unlock_at(5.days.ago)
override.save!
end
context "for a student" do
before do
@as_student, @as_instructor = overridable.unlock_ats_for(@student)
end
it "does not return instructor dates" do
@as_instructor.should be_nil
end
it "returns a relevant student date" do
@as_student.should_not be_nil
end
end
context "for a teacher" do
before do
@as_student, @as_instructor = overridable.unlock_ats_for(@teacher)
end
it "does not return a student date" do
@as_student.should be_nil
end
it "returns a list of instructor dates" do
@as_instructor.should_not be_nil
end
end
it "returns both for a user that's both a student and a teacher" do
course_with_ta(:course => course, :user => @student, :active_all => true)
as_student, as_instructor = overridable.unlock_ats_for(@student)
as_student.should_not be_nil
as_instructor.should_not be_nil
end
it "uses the overridden unlock date as the applicable unlock date" do
as_student, _ = overridable.unlock_ats_for(@student)
as_student.should == { :unlock_at => override.unlock_at }
end
it "includes the base unlock date in the list of unlock dates" do
_, as_instructor = overridable.unlock_ats_for(@teacher)
as_instructor.should include({ :base => true, :unlock_at => overridable.unlock_at })
end
it "includes visible unlock date overrides in the list of unlock dates" do
_, as_instructor = overridable.unlock_ats_for(@teacher)
as_instructor.should include({
:title => @course.default_section.name,
:unlock_at => override.unlock_at,
:override => override
})
end
it "excludes visible overrides that don't override unlock_at from the list of unlock dates" do
override.clear_unlock_at_override
override.save!
_, as_instructor = overridable.unlock_ats_for(@teacher)
as_instructor.size.should == 1
as_instructor.first[:base].should be_true
end
it "excludes overrides that aren't visible from the list of unlock dates" do
@enrollment = @teacher.enrollments.first
@enrollment.limit_privileges_to_course_section = true
@enrollment.save!
@section2 = @course.course_sections.create!
override.set = @section2
override.save!
_, as_instructor = overridable.unlock_ats_for(@teacher)
as_instructor.size.should == 1
as_instructor.first[:base].should be_true
end
end
describe "#lock_ats_for(user)" do
before :each do
course_with_student(:course => course, :active_all => true)
overridable.update_attributes(:unlock_at => 5.days.ago)
override.set = course.default_section
override.override_lock_at(2.days.ago)
override.save!
end
context "for a student" do
before do
@as_student, @as_instructor = overridable.lock_ats_for(@student)
end
it "does not return instructor dates" do
@as_instructor.should be_nil
end
it "returns a relevant student date" do
@as_student.should_not be_nil
end
end
context "for a teacher" do
before do
@as_student, @as_instructor = overridable.lock_ats_for(@teacher)
end
it "does not return a student date" do
@as_student.should be_nil
end
it "returns a list of instructor dates" do
@as_instructor.should_not be_nil
end
end
it "returns both for a user that's both a student and a teacher" do
course_with_ta(:course => course, :user => @student, :active_all => true)
as_student, as_instructor = overridable.lock_ats_for(@student)
as_student.should_not be_nil
as_instructor.should_not be_nil
end
it "uses the overridden lock date as the applicable lock date" do
as_student, _ = overridable.lock_ats_for(@student)
as_student.should == { :lock_at => override.lock_at }
end
it "includes the base lock date in the list of lock dates" do
_, as_instructor = overridable.lock_ats_for(@teacher)
as_instructor.should include({ :base => true, :lock_at => overridable.lock_at })
end
it "includes visible lock date overrides in the list of lock dates" do
_, as_instructor = overridable.lock_ats_for(@teacher)
as_instructor.detect { |a| a[:override].present? }.should == {
:title => course.default_section.name,
:lock_at => override.lock_at,
:override => override
}
end
it "excludes visible overrides that don't override lock_at from the list of lock dates" do
override.clear_lock_at_override
override.save!
_, as_instructor = overridable.lock_ats_for(@teacher)
as_instructor.size.should == 1
as_instructor.first[:base].should be_true
end
it "excludes overrides that aren't visible from the list of lock dates" do
@enrollment = @teacher.enrollments.first
@enrollment.limit_privileges_to_course_section = true
@enrollment.save!
@section2 = course.course_sections.create!
override.set = @section2
override.save!
_, as_instructor = overridable.lock_ats_for(@teacher)
as_instructor.size.should == 1
as_instructor.first[:base].should be_true
end
end
end
describe Assignment do
it_should_behave_like "an object whose dates are overridable"
let(:overridable) { assignment_model(:due_at => 5.days.ago) }
let(:overridable_type) { :assignment }
end
describe Quiz do
it_should_behave_like "an object whose dates are overridable"
let(:overridable) { quiz_model(:due_at => 5.days.ago) }
let(:overridable_type) { :quiz }
end

View File

@ -233,6 +233,12 @@ describe AssignmentOverride do
@override.set = @course.default_section
@override.should be_valid
end
it "is valid when the assignment is nil if it has a quiz" do
@override.assignment = nil
@override.quiz = quiz_model
@override.should be_valid
end
end
describe "title" do
@ -482,4 +488,54 @@ describe AssignmentOverride do
visible_overrides.should include @override2
end
end
describe "default_values" do
subject { override }
let(:override) { AssignmentOverride.new }
let(:quiz) { Quiz.new }
let(:assignment) { Assignment.new }
context "when the override belongs to a quiz" do
before do
override.quiz = quiz
end
context "that has an assignment" do
it "uses the quiz's assignment" do
override.quiz.assignment = assignment
override.send(:default_values)
override.assignment.should == assignment
end
end
context "that has no assignment" do
it "has a nil assignment" do
override.send(:default_values)
override.assignment.should be_nil
end
end
end
context "when the override belongs to an assignment" do
before do
override.assignment = assignment
end
context "that has a quiz" do
it "uses the assignment's quiz" do
override.assignment.quiz = quiz
override.send(:default_values)
override.quiz.should == quiz
end
end
context "that has no quiz" do
it "has a nil quiz" do
override.send(:default_values)
override.quiz.should be_nil
end
end
end
end
end

View File

@ -78,4 +78,46 @@ describe AssignmentOverrideStudent do
@override_student.valid? # trigger maintenance
@override_student.assignment_id.should == @override2.assignment_id
end
describe "default_values" do
let(:override_student) { AssignmentOverrideStudent.new }
let(:override) { AssignmentOverride.new }
let(:quiz_id) { 1 }
let(:assignment_id) { 2 }
before do
override_student.assignment_override = override
end
context "when the override has an assignment" do
before do
override.assignment_id = assignment_id
override_student.send(:default_values)
end
it "has the assignment's ID" do
override_student.assignment_id.should == assignment_id
end
it "has a nil quiz ID" do
override_student.quiz_id.should be_nil
end
end
context "when the override has a quiz and assignment" do
before do
override.assignment_id = assignment_id
override.quiz_id = quiz_id
override_student.send(:default_values)
end
it "has the assignment's ID" do
override_student.assignment_id.should == assignment_id
end
it "has the quiz's ID" do
override_student.quiz_id.should == quiz_id
end
end
end
end

View File

@ -598,357 +598,6 @@ describe Assignment do
end
end
it "should respond to #overridden_for(user)" do
student_in_course
@assignment = assignment_model(:course => @course, :due_at => 5.days.from_now)
@assignment.reload
@override = assignment_override_model(:assignment => @assignment)
@override.override_due_at(7.days.from_now)
@override.save!
@override.reload
@override_student = @override.assignment_override_students.build
@override_student.user = @student
@override_student.save!
@overridden = @assignment.overridden_for(@student)
@overridden.due_at.should == @override.due_at
end
describe "has_overrides?" do
let(:assignment) { assignment_model(:course => @course, :due_at => 5.days.from_now) }
it "returns true when it does" do
assignment_override_model(:assignment => assignment)
assignment.has_overrides?.should be_true
end
it "returns false when it doesn't" do
assignment.has_overrides?.should be_false
end
end
describe "#overrides_visible_to(user)" do
before :each do
@assignment = assignment_model
@override = assignment_override_model(:assignment => @assignment)
@override.set = @course.default_section
@override.save!
end
it "should delegate to visible_to on the active overrides by default" do
@expected_value = stub("expected value")
@assignment.active_assignment_overrides.expects(:visible_to).with(@teacher, @course).returns(@expected_value)
@assignment.overrides_visible_to(@teacher).should == @expected_value
end
it "should allow overriding the scope" do
@override.destroy
@assignment.overrides_visible_to(@teacher).should be_empty
@assignment.overrides_visible_to(@teacher, @assignment.assignment_overrides(true)).should == [@override]
end
it "should skip the visible_to application if the scope is already empty" do
@override.destroy
@assignment.active_assignment_overrides.expects(:visible_to).times(0)
@assignment.overrides_visible_to(@teacher)
end
it "should return a scope" do
# can't use "should respond_to", because that delegates to the instantiated Array
lambda{ @assignment.overrides_visible_to(@teacher).scoped({}) }.should_not raise_exception
end
end
describe "#due_dates_for(user)" do
before :each do
course_with_student(:active_all => true)
@assignment = assignment_model(:course => @course, :due_at => 5.days.ago)
@assignment.reload
@override = assignment_override_model(:assignment => @assignment)
@override.set = @course.default_section
@override.override_due_at(2.days.ago)
@override.save!
@override.reload
end
it "should not return the list of due dates for a student" do
_, as_instructor = @assignment.due_dates_for(@student)
as_instructor.should be_nil
end
it "should not return an applicable due date for a teacher" do
as_student, _ = @assignment.due_dates_for(@teacher)
as_student.should be_nil
end
it "should return the applicable due date for a student" do
as_student, _ = @assignment.due_dates_for(@student)
as_student.should_not be_nil
end
it "should return the list of due dates for a teacher" do
_, as_instructor = @assignment.due_dates_for(@teacher)
as_instructor.should_not be_nil
end
it "should return both for a user that's both a student and a teacher" do
course_with_ta(:course => @course, :user => @student, :active_all => true)
as_student, as_instructor = @assignment.due_dates_for(@student)
as_student.should_not be_nil
as_instructor.should_not be_nil
end
it "should use the overridden due date as the applicable due date" do
as_student, _ = @assignment.due_dates_for(@student)
as_student[:due_at].should == @override.due_at
as_student[:all_day].should == @override.all_day
as_student[:all_day_date].should == @override.all_day_date
end
it "should include the base due date in the list of due dates" do
_, as_instructor = @assignment.due_dates_for(@teacher)
as_instructor.should include({
:base => true,
:due_at => @assignment.due_at,
:all_day => @assignment.all_day,
:all_day_date => @assignment.all_day_date
})
end
it "should include visible due date overrides in the list of due dates" do
_, as_instructor = @assignment.due_dates_for(@teacher)
as_instructor.should include({
:title => @course.default_section.name,
:due_at => @override.due_at,
:all_day => @override.all_day,
:all_day_date => @override.all_day_date,
:override => @override
})
end
it "should exclude visible overrides that don't override due_at from the list of due dates" do
@override.clear_due_at_override
@override.save!
_, as_instructor = @assignment.due_dates_for(@teacher)
as_instructor.size.should == 1
as_instructor.first[:base].should be_true
end
it "should exclude overrides that aren't visible from the list of due dates" do
@enrollment = @teacher.enrollments.first
@enrollment.limit_privileges_to_course_section = true
@enrollment.save!
@section2 = @course.course_sections.create!
@override.set = @section2
@override.save!
_, as_instructor = @assignment.due_dates_for(@teacher)
as_instructor.size.should == 1
as_instructor.first[:base].should be_true
end
end
describe "due_date_hash" do
it "returns the due at, all day, and all day date params" do
due = 5.days.from_now
a = Assignment.new(:due_at => due)
a.due_date_hash.should == { :due_at => due, :all_day => false, :all_day_date => nil }
end
end
describe "observed_student_due_dates" do
it "returns a list of overridden due date hashes" do
a = Assignment.new
u = User.new
student1, student2 = [mock, mock]
{ student1 => '1', student2 => '2' }.each do |student, value|
a.expects(:overridden_for).with(student).returns \
mock(:due_date_hash => { :student => value })
end
ObserverEnrollment.expects(:observed_students).returns({student1 => [], student2 => []})
override_hashes = a.observed_student_due_dates(u).sort_by { |h| h[:student] }
override_hashes.should == [ { :student => '1' }, { :student => '2' } ]
end
end
describe "#unlock_ats_for(user)" do
before :each do
course_with_student(:active_all => true)
@assignment = assignment_model(:course => @course, :unlock_at => 2.days.ago)
@assignment.reload
@override = assignment_override_model(:assignment => @assignment)
@override.set = @course.default_section
@override.override_unlock_at(5.days.ago)
@override.save!
@override.reload
end
it "should not return the list of unlock dates for a student" do
_, as_instructor = @assignment.unlock_ats_for(@student)
as_instructor.should be_nil
end
it "should not return an applicable unlock date for a teacher" do
as_student, _ = @assignment.unlock_ats_for(@teacher)
as_student.should be_nil
end
it "should return the applicable unlock date for a student" do
as_student, _ = @assignment.unlock_ats_for(@student)
as_student.should_not be_nil
end
it "should return the list of unlock dates for a teacher" do
_, as_instructor = @assignment.unlock_ats_for(@teacher)
as_instructor.should_not be_nil
end
it "should return both for a user that's both a student and a teacher" do
course_with_ta(:course => @course, :user => @student, :active_all => true)
as_student, as_instructor = @assignment.unlock_ats_for(@student)
as_student.should_not be_nil
as_instructor.should_not be_nil
end
it "should use the overridden unlock date as the applicable unlock date" do
as_student, _ = @assignment.unlock_ats_for(@student)
as_student.should == { :unlock_at => @override.unlock_at }
end
it "should include the base unlock date in the list of unlock dates" do
_, as_instructor = @assignment.unlock_ats_for(@teacher)
as_instructor.should include({ :base => true, :unlock_at => @assignment.unlock_at })
end
it "should include visible unlock date overrides in the list of unlock dates" do
_, as_instructor = @assignment.unlock_ats_for(@teacher)
as_instructor.should include({
:title => @course.default_section.name,
:unlock_at => @override.unlock_at,
:override => @override
})
end
it "should exclude visible overrides that don't override unlock_at from the list of unlock dates" do
@override.clear_unlock_at_override
@override.save!
_, as_instructor = @assignment.unlock_ats_for(@teacher)
as_instructor.size.should == 1
as_instructor.first[:base].should be_true
end
it "should exclude overrides that aren't visible from the list of unlock dates" do
@enrollment = @teacher.enrollments.first
@enrollment.limit_privileges_to_course_section = true
@enrollment.save!
@section2 = @course.course_sections.create!
@override.set = @section2
@override.save!
_, as_instructor = @assignment.unlock_ats_for(@teacher)
as_instructor.size.should == 1
as_instructor.first[:base].should be_true
end
end
describe "#lock_ats_for(user)" do
before :each do
course_with_student(:active_all => true)
@assignment = assignment_model(:course => @course, :lock_at => 5.days.ago)
@assignment.reload
@override = assignment_override_model(:assignment => @assignment)
@override.set = @course.default_section
@override.override_lock_at(2.days.ago)
@override.save!
@override.reload
end
it "should not return the list of lock dates for a student" do
_, as_instructor = @assignment.lock_ats_for(@student)
as_instructor.should be_nil
end
it "should not return an applicable lock date for a teacher" do
as_student, _ = @assignment.lock_ats_for(@teacher)
as_student.should be_nil
end
it "should return the applicable lock date for a student" do
as_student, _ = @assignment.lock_ats_for(@student)
as_student.should_not be_nil
end
it "should return the list of lock dates for a teacher" do
_, as_instructor = @assignment.lock_ats_for(@teacher)
as_instructor.should_not be_nil
end
it "should return both for a user that's both a student and a teacher" do
course_with_ta(:course => @course, :user => @student, :active_all => true)
as_student, as_instructor = @assignment.lock_ats_for(@student)
as_student.should_not be_nil
as_instructor.should_not be_nil
end
it "should use the overridden lock date as the applicable lock date" do
as_student, _ = @assignment.lock_ats_for(@student)
as_student.should == { :lock_at => @override.lock_at }
end
it "should include the base lock date in the list of lock dates" do
_, as_instructor = @assignment.lock_ats_for(@teacher)
as_instructor.should include({ :base => true, :lock_at => @assignment.lock_at })
end
it "should include visible lock date overrides in the list of lock dates" do
_, as_instructor = @assignment.lock_ats_for(@teacher)
as_instructor.detect { |a| a[:override].present? }.should == {
:title => @course.default_section.name,
:lock_at => @override.lock_at,
:override => @override
}
end
it "should exclude visible overrides that don't override lock_at from the list of lock dates" do
@override.clear_lock_at_override
@override.save!
_, as_instructor = @assignment.lock_ats_for(@teacher)
as_instructor.size.should == 1
as_instructor.first[:base].should be_true
end
it "should exclude overrides that aren't visible from the list of lock dates" do
@enrollment = @teacher.enrollments.first
@enrollment.limit_privileges_to_course_section = true
@enrollment.save!
@section2 = @course.course_sections.create!
@override.set = @section2
@override.save!
_, as_instructor = @assignment.lock_ats_for(@teacher)
as_instructor.size.should == 1
as_instructor.first[:base].should be_true
end
end
context "concurrent inserts" do
def concurrent_inserts
assignment_model
@ -2679,6 +2328,78 @@ describe Assignment do
@assignment.submitted_count.should == 50
end
end
describe "linking overrides with quizzes" do
let(:course) { course_model }
let(:assignment) { assignment_model(:course => course, :due_at => 5.days.from_now).reload }
let(:override) { assignment_override_model(:assignment => assignment) }
let(:override_student) { override.assignment_override_students.build }
before do
override.override_due_at(7.days.from_now)
override.save!
student_in_course(:course => course)
override_student.user = @student
override_student.save!
end
context "before the assignment has a quiz" do
context "override" do
it "has a nil quiz" do
override.quiz.should be_nil
end
it "has an assignment" do
override.assignment.should == assignment
end
end
context "override student" do
it "has a nil quiz" do
override_student.quiz.should be_nil
end
it "has an assignment" do
override_student.assignment.should == assignment
end
end
end
context "once the assignment changes to a quiz submission" do
before do
assignment.submission_types = "online_quiz"
assignment.save
assignment.reload
override.reload
override_student.reload
end
it "has a quiz" do
assignment.quiz.should be_present
end
context "override" do
it "has an assignment" do
override.assignment.should == assignment
end
it "has the assignment's quiz" do
override.quiz.should == assignment.quiz
end
end
context "override student" do
it "has an assignment" do
override_student.assignment.should == assignment
end
it "has the assignment's quiz" do
override_student.quiz.should == assignment.quiz
end
end
end
end
end
def setup_assignment_with_group

View File

@ -969,4 +969,83 @@ describe Quiz do
@quiz.has_student_submissions?.should be_true
end
end
describe "linking overrides with assignments" do
let(:course) { course_model }
let(:quiz) { quiz_model(:course => course, :due_at => 5.days.from_now).reload }
let(:override) { assignment_override_model(:quiz => quiz) }
let(:override_student) { override.assignment_override_students.build }
before do
override.override_due_at(7.days.from_now)
override.save!
student_in_course(:course => course)
override_student.user = @student
override_student.save!
end
context "before the quiz has an assignment" do
context "override" do
it "has a quiz" do
override.quiz.should == quiz
end
it "has a nil assignment" do
override.assignment.should be_nil
end
end
context "override student" do
it "has a quiz" do
override_student.quiz.should == quiz
end
it "has a nil assignment" do
override_student.assignment.should be_nil
end
end
end
context "once the quiz is published" do
before do
# publish the quiz
quiz.workflow_state = 'available'
quiz.save
override.reload
override_student.reload
end
context "override" do
it "has a quiz" do
override.quiz.should == quiz
end
it "has the quiz's assignment" do
override.assignment.should == quiz.assignment
end
end
context "override student" do
it "has a quiz" do
override_student.quiz.should == quiz
end
it "has the quiz's assignment" do
override_student.assignment.should == quiz.assignment
end
end
end
context "when the assignment ID doesn't change" do
it "doesn't update overrides" do
quiz.expects(:link_assignment_overrides).once
# publish the quiz
quiz.workflow_state = 'available'
quiz.save
quiz.expects(:link_assignment_overrides).never
quiz.save
end
end
end
end