add the ability to regrade a quiz
This commit gives teachers the ability to regrade quizzes by using different options per quiz question: * Current Correct Only * Full Credit (regardless of answer choice) * Previous and Current Correct * No Point change (for updating the display of a question) Test Plan: You'll want to run through each question regrade option making sure scores change appropriately. "I seldom end up where I wanted to go, but almost always end up where I need to be." - Douglas Adams Change-Id: I9dbb88154cd3ac630bf59dbf3e997a87f75649dc Reviewed-on: https://gerrit.instructure.com/22018 Tested-by: Jenkins <jenkins@instructure.com> Reviewed-by: Derek DeVries <ddevries@instructure.com> QA-Review: Myller de Araujo <myller@instructure.com> Product-Review: Stanley Stuart <stanley@instructure.com>
This commit is contained in:
parent
85236b90c2
commit
9b76539a3e
|
@ -3,6 +3,7 @@ require [
|
|||
'quiz_show'
|
||||
'quiz_rubric'
|
||||
'message_students'
|
||||
'jquery.disableWhileLoading'
|
||||
], (inputMethods) ->
|
||||
$ ->
|
||||
inputMethods.setWidths()
|
||||
|
@ -12,3 +13,12 @@ require [
|
|||
$(".download_submissions_link").click (event) ->
|
||||
event.preventDefault()
|
||||
INST.downloadSubmissions($(this).attr('href'))
|
||||
|
||||
# load in regrade versions
|
||||
if ENV.SUBMISSION_VERSIONS_URL && !ENV.IS_SURVEY
|
||||
versions = $("#quiz-submission-version-table")
|
||||
versions.css(height: "100px")
|
||||
dfd = $.get ENV.SUBMISSION_VERSIONS_URL, (data) ->
|
||||
versions.html(data)
|
||||
versions.css(height: "auto")
|
||||
versions.disableWhileLoading(dfd)
|
|
@ -57,7 +57,9 @@ class QuizQuestionsController < ApplicationController
|
|||
if authorized_action(@quiz, @current_user, :update)
|
||||
@question = @quiz.quiz_questions.find(params[:id])
|
||||
question_data = params[:question]
|
||||
question_data[:regrade_user] = @current_user
|
||||
question_data ||= {}
|
||||
|
||||
if question_data[:quiz_group_id]
|
||||
@group = @quiz.quiz_groups.find(question_data[:quiz_group_id])
|
||||
if question_data[:quiz_group_id] != @question.quiz_group_id
|
||||
|
@ -65,6 +67,7 @@ class QuizQuestionsController < ApplicationController
|
|||
@question.position = @group.quiz_questions.length
|
||||
end
|
||||
end
|
||||
|
||||
@question.question_data = question_data
|
||||
@question.save
|
||||
@quiz.did_edit if @quiz.created?
|
||||
|
|
|
@ -25,7 +25,7 @@ class QuizzesController < ApplicationController
|
|||
before_filter :require_context
|
||||
add_crumb(proc { t('#crumbs.quizzes', "Quizzes") }) { |c| c.send :named_context_url, c.instance_variable_get("@context"), :context_quizzes_url }
|
||||
before_filter { |c| c.active_tab = "quizzes" }
|
||||
before_filter :get_quiz, :only => [:statistics, :edit, :show, :reorder, :history, :update, :destroy, :moderate, :filters, :read_only, :managed_quiz_data]
|
||||
before_filter :get_quiz, :only => [:statistics, :edit, :show, :reorder, :history, :update, :destroy, :moderate, :filters, :read_only, :managed_quiz_data, :submission_versions]
|
||||
before_filter :set_download_submission_dialog_title , only: [:show,:statistics]
|
||||
# The number of questions that can display "details". After this number, the "Show details" option is disabled
|
||||
# and the data is not even loaded.
|
||||
|
@ -158,13 +158,17 @@ class QuizzesController < ApplicationController
|
|||
flash[:notice] = t('notices.has_submissions_already', "Keep in mind, some students have already taken or started taking this quiz")
|
||||
end
|
||||
|
||||
regrade_options = Hash[@quiz.current_quiz_question_regrades.map do |qqr|
|
||||
[qqr.quiz_question_id, qqr.regrade_option]
|
||||
end]
|
||||
sections = @context.course_sections.active
|
||||
hash = { :ASSIGNMENT_ID => @assigment.present? ? @assignment.id : nil,
|
||||
:ASSIGNMENT_OVERRIDES => assignment_overrides_json(@quiz.overrides_visible_to(@current_user)),
|
||||
:QUIZ => quiz_json(@quiz, @context, @current_user, session),
|
||||
:SECTION_LIST => sections.map { |section| { :id => section.id, :name => section.name } },
|
||||
:QUIZZES_URL => polymorphic_url([@context, :quizzes]),
|
||||
:CONTEXT_ACTION_SOURCE => :quizzes }
|
||||
:CONTEXT_ACTION_SOURCE => :quizzes,
|
||||
:REGRADE_OPTIONS => regrade_options }
|
||||
append_sis_data(hash)
|
||||
js_env(hash)
|
||||
render :action => "new"
|
||||
|
@ -226,11 +230,9 @@ class QuizzesController < ApplicationController
|
|||
|
||||
@assignment = @quiz.assignment
|
||||
@assignment = @assignment.overridden_for(@current_user) if @assignment
|
||||
@submission = @quiz.quiz_submissions.find_by_user_id(@current_user.id, :order => 'created_at') rescue nil
|
||||
if !@current_user || (params[:preview] && @quiz.grants_right?(@current_user, session, :update))
|
||||
user_code = temporary_user_code
|
||||
@submission = @quiz.quiz_submissions.find_by_temporary_user_code(user_code)
|
||||
end
|
||||
|
||||
@submission = get_submission
|
||||
|
||||
@just_graded = false
|
||||
if @submission && @submission.needs_grading?(!!params[:take])
|
||||
@submission.grade_submission(:finished_at => @submission.end_at)
|
||||
|
@ -240,7 +242,9 @@ class QuizzesController < ApplicationController
|
|||
if @submission
|
||||
upload_url = api_v1_quiz_submission_create_file_path(:course_id => @context.id, :quiz_id => @quiz.id)
|
||||
js_env :UPLOAD_URL => upload_url
|
||||
js_env :SUBMISSION_VERSIONS_URL => polymorphic_url([@context, @quiz, 'submission_versions'])
|
||||
end
|
||||
|
||||
setup_attachments
|
||||
submission_counts if @quiz.grants_right?(@current_user, session, :grade) || @quiz.grants_right?(@current_user, session, :read_statistics)
|
||||
@stored_params = (@submission.temporary_data rescue nil) if params[:take] && @submission && (@submission.untaken? || @submission.preview?)
|
||||
|
@ -447,13 +451,14 @@ class QuizzesController < ApplicationController
|
|||
end
|
||||
@current_submission = @submission
|
||||
@version_instances = @submission.submitted_versions.sort_by{|v| v.version_number }
|
||||
@versions = get_versions
|
||||
params[:version] ||= @version_instances[0].version_number if @submission.untaken? && !@version_instances.empty?
|
||||
@current_version = true
|
||||
@version_number = "current"
|
||||
if params[:version]
|
||||
@version_number = params[:version].to_i
|
||||
@unversioned_submission = @submission
|
||||
@submission = @version_instances.detect{|s| s.version_number >= @version_number}
|
||||
@submission = @versions.detect{|s| s.version_number >= @version_number}
|
||||
@submission ||= @unversioned_submission.versions.get(params[:version]).model
|
||||
@current_version = (@current_submission.version_number == @submission.version_number)
|
||||
@version_number = "current" if @current_version
|
||||
|
@ -637,6 +642,19 @@ class QuizzesController < ApplicationController
|
|||
@headers = !params[:headless] && !session[:headless_quiz]
|
||||
end
|
||||
|
||||
def submission_versions
|
||||
if authorized_action(@quiz, @current_user, :read)
|
||||
@submission = get_submission
|
||||
@versions = get_versions
|
||||
|
||||
if @versions.size > 0
|
||||
render :layout => false
|
||||
else
|
||||
render :nothing => true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def get_quiz
|
||||
|
@ -645,6 +663,20 @@ class QuizzesController < ApplicationController
|
|||
@quiz
|
||||
end
|
||||
|
||||
def get_submission
|
||||
submission = @quiz.quiz_submissions.find_by_user_id(@current_user.id, :order => 'created_at') rescue nil
|
||||
if !@current_user || (params[:preview] && @quiz.grants_right?(@current_user, session, :update))
|
||||
user_code = temporary_user_code
|
||||
submission = @quiz.quiz_submissions.find_by_temporary_user_code(user_code)
|
||||
end
|
||||
|
||||
submission
|
||||
end
|
||||
|
||||
def get_versions
|
||||
@submission.submitted_attempts
|
||||
end
|
||||
|
||||
# if this returns false, it's rendering or redirecting, so return from the
|
||||
# action that called it
|
||||
def check_lockdown_browser(security_level, redirect_return_url)
|
||||
|
|
|
@ -453,4 +453,16 @@ module QuizzesHelper
|
|||
end
|
||||
end
|
||||
|
||||
def has_regraded_version?(versions)
|
||||
versions.detect {|v| v.score_before_regrade.present? }
|
||||
end
|
||||
|
||||
def submission_has_regrade?(submission)
|
||||
submission && submission.score_before_regrade.present?
|
||||
end
|
||||
|
||||
def score_affected_by_regrade?(submission)
|
||||
submission && submission.score_before_regrade != submission.score
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -119,6 +119,8 @@
|
|||
delay_for: <%= 60*60 %>
|
||||
- name: Submission Grade Changed
|
||||
delay_for: <%= 5*60 %>
|
||||
- name: Quiz Regrade Finished
|
||||
delay_for: 0
|
||||
|
||||
- category: Grading Policies
|
||||
notifications:
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
<% define_content :link do %>
|
||||
<%= HostUrl.protocol %>://<%= HostUrl.context_host(asset.quiz.context) %>/<%= polymorphic_path([asset.quiz.context,asset.quiz]) %>
|
||||
<% end %>
|
||||
|
||||
<% define_content :subject do %>
|
||||
<%= t :subject, "Quiz Regrade Finished: %{quiz}, %{context}", :quiz => asset.quiz.title, :context => asset.quiz.context.name %>
|
||||
<% end %>
|
||||
|
||||
<%= t :body, "A regrade has finsihed for the quiz %{quiz}", :quiz => asset.quiz %>
|
||||
|
||||
<%= t :link_message, "You can view the quiz here:" %>
|
||||
<%= content :link %>
|
|
@ -0,0 +1,16 @@
|
|||
<% define_content :link do %>
|
||||
<%= HostUrl.protocol %>://<%= HostUrl.context_host(asset.quiz.context) %>/<%= polymorphic_path([asset.quiz.context,asset.quiz]) %>
|
||||
<% end %>
|
||||
|
||||
<p>
|
||||
<% define_content :subject do %>
|
||||
<%= t :subject, "Quiz Regrade Finished: %{quiz}, %{context}", :quiz => asset.quiz.title, :context => asset.quiz.context.name %>
|
||||
<% end %>
|
||||
</p>
|
||||
|
||||
<p><%= t :body, "A regrade has finsihed for the quiz %{quiz}", :quiz => asset.quiz %></p>
|
||||
|
||||
<p>
|
||||
<%= t :link_message, "You can view the quiz here:" %>
|
||||
<a href="<%= content :link %>"><%= t :link_message, "You can view the quiz here:" %></a>
|
||||
</p>
|
|
@ -0,0 +1,5 @@
|
|||
<% define_content :link do %>
|
||||
<%= HostUrl.protocol %>://<%= HostUrl.context_host(asset.quiz.context) %>/<%= polymorphic_path([asset.quiz.context,asset.quiz]) %>
|
||||
<% end %>
|
||||
|
||||
<p><%= t :body, "Quiz Regrade Finished: %{title}", :title => asset.quiz.title %></p>
|
|
@ -0,0 +1,3 @@
|
|||
<%= t :body_sms, "Quiz Regrade Finished for %{title} in %{context}.", :title => asset.quiz.title, :context => asset.quiz.context %>
|
||||
|
||||
<%= t :more_info, "More info at %{url}", :url => HostUrl.context_host(asset.quiz.context) %>
|
|
@ -0,0 +1,10 @@
|
|||
<% define_content :link do %>
|
||||
<%= HostUrl.protocol %>://<%= polymorphic_path([asset.quiz.context,asset.quiz]) %>
|
||||
<% end %>
|
||||
|
||||
<% define_content :subject do %>
|
||||
<%= t :subject, "Quiz Regraded: %{quiz}, %{context}", :quiz => asset.quiz.title, :context => asset.quiz.context.name %>
|
||||
<% end %>
|
||||
|
||||
<%= t :body, "A regrade has finished for your quiz %{title}.", :title => asset.quiz.title, :wrapper => "<b><a href=\"#{content :link}\">\\1</a></b>" %>
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
<% define_content :link do %>
|
||||
<%= HostUrl.protocol %>://<%= HostUrl.context_host(asset.quiz.context) %>/<%= polymorphic_path([asset.quiz.context,asset.quiz]) %>
|
||||
<% end %>
|
||||
<%= t :body, "Canvas Alert - Quiz Regraded : %{quiz}", :quiz => asset.quiz.title %>
|
|
@ -227,6 +227,7 @@ class AssessmentQuestion < ActiveRecord::Base
|
|||
question = HashWithIndifferentAccess.new
|
||||
qdata = qdata.with_indifferent_access
|
||||
previous_data = assessment_question.question_data rescue {}
|
||||
question[:regrade_option] = qdata[:regrade_option] if qdata[:regrade_option].present?
|
||||
question[:points_possible] = (qdata[:points_possible] || previous_data[:points_possible] || 0.0).to_f
|
||||
question[:correct_comments] = check_length(qdata[:correct_comments] || previous_data[:correct_comments] || "", 'correct comments', 5.kilobyte)
|
||||
question[:incorrect_comments] = check_length(qdata[:incorrect_comments] || previous_data[:incorrect_comments] || "", 'incorrect comments', 5.kilobyte)
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
#
|
||||
|
||||
require 'quiz_question_link_migrator'
|
||||
require 'quiz_regrading'
|
||||
|
||||
class Quiz < ActiveRecord::Base
|
||||
include Workflow
|
||||
|
@ -43,6 +44,7 @@ class Quiz < ActiveRecord::Base
|
|||
has_many :quiz_groups, :dependent => :destroy, :order => 'position'
|
||||
has_many :quiz_statistics, :class_name => 'QuizStatistics', :order => 'created_at'
|
||||
has_many :attachments, :as => :context, :dependent => :destroy
|
||||
has_many :quiz_regrades
|
||||
belongs_to :context, :polymorphic => true
|
||||
belongs_to :assignment
|
||||
belongs_to :cloned_item
|
||||
|
@ -62,6 +64,7 @@ class Quiz < ActiveRecord::Base
|
|||
before_save :set_defaults
|
||||
after_save :update_assignment
|
||||
after_save :touch_context
|
||||
after_save :regrade_if_published
|
||||
|
||||
serialize :quiz_data
|
||||
|
||||
|
@ -579,6 +582,7 @@ class Quiz < ActiveRecord::Base
|
|||
submission.quiz_data = user_questions
|
||||
submission.quiz_version = self.version_number
|
||||
submission.started_at = Time.now
|
||||
submission.score_before_regrade = nil
|
||||
submission.end_at = nil
|
||||
submission.end_at = submission.started_at + (self.time_limit.to_f * 60.0) if self.time_limit
|
||||
# Admins can take the full quiz whenever they want
|
||||
|
@ -1127,6 +1131,10 @@ class Quiz < ActiveRecord::Base
|
|||
scope :active, where("quizzes.workflow_state<>'deleted'")
|
||||
scope :not_for_assignment, where(:assignment_id => nil)
|
||||
|
||||
def teachers
|
||||
context.teacher_enrollments.map(&:user)
|
||||
end
|
||||
|
||||
def migrate_file_links
|
||||
QuizQuestionLinkMigrator.migrate_file_links_in_quiz(self)
|
||||
end
|
||||
|
@ -1225,4 +1233,20 @@ class Quiz < ActiveRecord::Base
|
|||
end
|
||||
end
|
||||
|
||||
def regrade_if_published
|
||||
unless unpublished_changes?
|
||||
QuizRegrader.send_later_if_production(:regrade!, self)
|
||||
end
|
||||
true
|
||||
end
|
||||
|
||||
def current_regrade
|
||||
QuizRegrade.where(quiz_id: id, quiz_version: version_number).
|
||||
includes(:quiz_question_regrades => :quiz_question).first
|
||||
end
|
||||
|
||||
def current_quiz_question_regrades
|
||||
current_regrade ? current_regrade.quiz_question_regrades : []
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -48,6 +48,10 @@ class QuizQuestion < ActiveRecord::Base
|
|||
end
|
||||
|
||||
def question_data=(data)
|
||||
if data[:regrade_option]
|
||||
update_question_regrade(data[:regrade_option], data[:regrade_user])
|
||||
end
|
||||
|
||||
if data.is_a?(String)
|
||||
data = ActiveSupport::JSON.decode(data) rescue nil
|
||||
elsif data.class == Hash
|
||||
|
@ -174,4 +178,16 @@ class QuizQuestion < ActiveRecord::Base
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def update_question_regrade(regrade_option, regrade_user)
|
||||
regrade = QuizRegrade.find_or_create_by_quiz_id_and_quiz_version(quiz.id, quiz.version_number) do |qr|
|
||||
qr.user_id = regrade_user.id
|
||||
end
|
||||
|
||||
question_regrade = QuizQuestionRegrade.find_or_initialize_by_quiz_question_id_and_quiz_regrade_id(id, regrade.id)
|
||||
question_regrade.regrade_option = regrade_option
|
||||
question_regrade.save!
|
||||
end
|
||||
end
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
class QuizQuestionRegrade < ActiveRecord::Base
|
||||
attr_accessible :quiz_question_id, :quiz_regrade_id, :regrade_option
|
||||
belongs_to :quiz_question
|
||||
belongs_to :quiz_regrade
|
||||
|
||||
validates_presence_of :quiz_question_id
|
||||
validates_presence_of :quiz_regrade_id
|
||||
|
||||
delegate :question_data, to: :quiz_question
|
||||
end
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
class QuizRegrade < ActiveRecord::Base
|
||||
attr_accessible :user_id, :quiz_id, :quiz_version
|
||||
belongs_to :quiz
|
||||
belongs_to :user
|
||||
has_many :quiz_regrade_runs
|
||||
has_many :quiz_question_regrades
|
||||
|
||||
validates_presence_of :quiz_version
|
||||
validates_presence_of :quiz_id
|
||||
validates_presence_of :user_id
|
||||
|
||||
delegate :teachers, to: :quiz
|
||||
end
|
|
@ -0,0 +1,24 @@
|
|||
class QuizRegradeRun < ActiveRecord::Base
|
||||
belongs_to :quiz_regrade
|
||||
attr_accessible :quiz_regrade_id, :started_at, :finished_at
|
||||
validates_presence_of :quiz_regrade_id
|
||||
|
||||
def self.perform(regrade)
|
||||
run = create!(quiz_regrade_id: regrade.id, started_at: Time.now)
|
||||
yield
|
||||
run.finished_at = Time.now
|
||||
run.save!
|
||||
end
|
||||
|
||||
has_a_broadcast_policy
|
||||
set_broadcast_policy do |policy|
|
||||
policy.dispatch :quiz_regrade_finished
|
||||
policy.to { teachers }
|
||||
policy.whenever do |run|
|
||||
old,new = run.changes['finished_at']
|
||||
!!(new && old.nil?)
|
||||
end
|
||||
end
|
||||
|
||||
delegate :teachers, :quiz, to: :quiz_regrade
|
||||
end
|
|
@ -18,7 +18,7 @@
|
|||
|
||||
class QuizSubmission < ActiveRecord::Base
|
||||
include Workflow
|
||||
attr_accessible :quiz, :user, :temporary_user_code, :submission_data
|
||||
attr_accessible :quiz, :user, :temporary_user_code, :submission_data, :score_before_regrade
|
||||
attr_readonly :quiz_id, :user_id
|
||||
validates_presence_of :quiz_id
|
||||
|
||||
|
@ -341,10 +341,17 @@ class QuizSubmission < ActiveRecord::Base
|
|||
end
|
||||
|
||||
def highest_score_so_far(exclude_version_id=nil)
|
||||
scores = []
|
||||
scores << self.score if self.score
|
||||
scores += versions.reload.reject{|v| v.id == exclude_version_id}.map{|v| v.model.score || 0.0} rescue []
|
||||
scores.max
|
||||
scores = {}
|
||||
scores[attempt] = self.score if self.score
|
||||
|
||||
versions = self.versions.reload.reject {|v| v.id == exclude_version_id } rescue []
|
||||
|
||||
# only most recent version for each attempt - some have regraded a version
|
||||
versions.sort {|v| v.number }.reverse.each do |ver|
|
||||
scores[ver.model.attempt] ||= ver.model.score || 0.0
|
||||
end
|
||||
|
||||
scores.values.max
|
||||
end
|
||||
private :highest_score_so_far
|
||||
|
||||
|
@ -445,6 +452,17 @@ class QuizSubmission < ActiveRecord::Base
|
|||
end
|
||||
end
|
||||
|
||||
def attempt_versions
|
||||
versions = self.versions.order("number desc").each_with_object({}) do |ver, hash|
|
||||
hash[ver.model.attempt] ||= ver
|
||||
end
|
||||
versions.sort.map {|attempt, version| version }
|
||||
end
|
||||
|
||||
def submitted_attempts
|
||||
attempt_versions.map {|ver| ver.model }
|
||||
end
|
||||
|
||||
def attempts_left
|
||||
return -1 if self.quiz.allowed_attempts < 0
|
||||
[0, self.quiz.allowed_attempts - (self.attempt || 0) + (self.extra_attempts || 0)].max
|
||||
|
@ -474,6 +492,7 @@ class QuizSubmission < ActiveRecord::Base
|
|||
@user_answers.each do |answer|
|
||||
self.workflow_state = "pending_review" if answer[:correct] == "undefined"
|
||||
end
|
||||
self.score_before_regrade = nil
|
||||
self.finished_at = Time.now
|
||||
self.manually_unlocked = nil
|
||||
self.finished_at = opts[:finished_at] if opts[:finished_at]
|
||||
|
@ -486,7 +505,12 @@ class QuizSubmission < ActiveRecord::Base
|
|||
end
|
||||
self.context_module_action
|
||||
track_outcomes(self.attempt)
|
||||
true
|
||||
quiz = self.quiz
|
||||
previous_version = quiz.versions.where(number: quiz_version).first
|
||||
if previous_version && quiz_version != quiz.version_number
|
||||
quiz = previous_version.model.reload
|
||||
end
|
||||
QuizRegrader.regrade!(quiz, [self])
|
||||
end
|
||||
|
||||
# Updates a simply_versioned version instance in-place. We want
|
||||
|
@ -678,6 +702,11 @@ class QuizSubmission < ActiveRecord::Base
|
|||
self.validation_token.blank? || self.validation_token == token
|
||||
end
|
||||
|
||||
# TODO: this could probably be put in as a convenience method in simply_versioned
|
||||
def save_with_versioning!
|
||||
self.with_versioning(true) { self.save! }
|
||||
end
|
||||
|
||||
# evizitei: these 3 delegations allow quiz submissions to be used in
|
||||
# templates designed for regular submissions. Any additional functionality
|
||||
# put into those templates will need to be provided in both submissions and
|
||||
|
|
|
@ -447,7 +447,7 @@ div#content
|
|||
|
||||
.text
|
||||
:clear left
|
||||
:padding 5px 20px
|
||||
:padding 5px 20px 20px 20px
|
||||
|
||||
.button-container
|
||||
:clear both
|
||||
|
@ -1455,3 +1455,71 @@ ul#quiz_versions
|
|||
|
||||
#quiz-draft-state
|
||||
padding: 10px
|
||||
font-weight: bold
|
||||
&.published
|
||||
color: #007711
|
||||
&.not_published
|
||||
color: #999999
|
||||
|
||||
.regrade-options
|
||||
margin-top: 12px
|
||||
color: #333
|
||||
padding: 2px 10px 13px 10px
|
||||
|
||||
h3
|
||||
margin: 0
|
||||
text-transform: uppercase
|
||||
font-size: 100%
|
||||
font-weight: bold
|
||||
|
||||
span
|
||||
font-weight: normal
|
||||
text-transform: none
|
||||
|
||||
.checkbox
|
||||
padding-left: 12px
|
||||
|
||||
input
|
||||
height: 16px
|
||||
margin: 0 9px 0 0
|
||||
|
||||
.regrade-options label
|
||||
display: block
|
||||
|
||||
.user-regrade-points
|
||||
color: orange
|
||||
|
||||
.regraded-warning
|
||||
margin: 0 0 10px 0
|
||||
|
||||
#quiz-submission-version-table
|
||||
margin: 30px 0 0 0
|
||||
@include clearfix
|
||||
|
||||
.desc
|
||||
width: 22%
|
||||
float: left
|
||||
margin-right: 2%
|
||||
text-align: right
|
||||
font-size: 110%
|
||||
color: #555
|
||||
|
||||
table
|
||||
width: 65%
|
||||
float: left
|
||||
border-color: #a7acb3
|
||||
|
||||
td
|
||||
padding: 4px 8px
|
||||
border-color: #a7acb3
|
||||
|
||||
thead td.regraded
|
||||
color: white
|
||||
background-color: #f89508
|
||||
text-shadow: 0 -1px 0 #cf9b47
|
||||
border-top: none
|
||||
|
||||
td.regraded
|
||||
border-top: 1px solid #C4AE90
|
||||
background-color: $warningBackground
|
||||
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
<div class="regrade-options alert border-round">
|
||||
<h3>
|
||||
{{#t "regrade_options"}}
|
||||
Regrade Options <span>(for students who have already answered this question):</span>
|
||||
{{/t}}
|
||||
</h3>
|
||||
|
||||
<label class="checkbox">
|
||||
<input type="radio" name="regrade_option" class="regrade_option" value="current_and_previous_correct" {{checkedIf regradeOption "current_and_previous_correct"}}>
|
||||
{{#t "no_scores_reduced"}}
|
||||
Award points for both corrected and previously correct answers (<em>no scores will be reduced</em>)
|
||||
{{/t}}
|
||||
</label>
|
||||
|
||||
<label class="checkbox">
|
||||
<input type="radio" name="regrade_option" class="regrade_option" value="current_correct_only" {{checkedIf regradeOption "current_correct_only"}} />
|
||||
{{#t "some_scores_reduced"}}
|
||||
Only award points for the correct answer (<em>some students' scores may be reduced</em>)
|
||||
{{/t}}
|
||||
</label>
|
||||
|
||||
<label class="checkbox">
|
||||
<input type="radio" name="regrade_option" class="regrade_option" value="full_credit" {{checkedIf regradeOption "full_credit"}} >
|
||||
{{#t "give_everyone_full_credit"}}
|
||||
Give everyone full credit for this question.
|
||||
{{/t}}
|
||||
</label>
|
||||
|
||||
<label class="checkbox">
|
||||
<input type="radio" name="regrade_option" class="regrade_option" value="no_regrade" {{checkedIf regradeOption "no_regrade"}} >
|
||||
{{#t "update_question_without_regrading"}}
|
||||
Update question without regrading
|
||||
{{/t}}
|
||||
</label>
|
||||
|
||||
</div>
|
||||
|
|
@ -36,6 +36,7 @@
|
|||
<!-- Display skipped -->
|
||||
<% else %>
|
||||
<div class="answer answer_for_<%= hash_get(answer, :blank_id) %> <%= "hide_right_arrow" if hide_right_arrow %> <%= 'skipped' if should_skip %> <%= 'wrong_answer' if wrong_answer && show_correct_answers %> <%= 'no_answer' if no_answer %> <%= "selected_answer" if selected_answer %> <%= correct_answer_class %>" id="answer_<%= hash_get(answer, :id, "template") %>" style="<%= hidden unless answer %>" title="<%= t(:selected_answer, "You selected this answer.") if selected_answer %> <%= t(:correct_answer, "This was the correct answer.") if correct_answer && show_correct_answers %>">
|
||||
<span class='hidden id'><%= answer_id %></span>
|
||||
<% if !user_answer || question_type.display_answers != "xsingle" %>
|
||||
<div class="select_answer answer_type" <%= hidden(true) unless answer_type == "select_answer" %>>
|
||||
<% if %w{radio checkbox}.include?(question_type.entry_type) %>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<%
|
||||
question = display_question
|
||||
<%
|
||||
question = display_question
|
||||
question_type = answer_type(question)
|
||||
user_answer ||= nil
|
||||
user_answer_hash ||= {}
|
||||
|
@ -43,16 +43,28 @@
|
|||
<% if hash_get(user_answer, :correct) == "undefined" %>
|
||||
<%= t(:not_yet_graded, 'Not yet graded') %>
|
||||
<% else %>
|
||||
<%= render_score(hash_get(user_answer, :points)) %>
|
||||
<% if hash_get(user_answer,:score_before_regrade)%>
|
||||
<%= t('original_score', 'Original Score:') %>
|
||||
<%= render_score(hash_get(user_answer, :score_before_regrade)) %>
|
||||
<% else %>
|
||||
<%= render_score(hash_get(user_answer, :points)) %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% question[:points_possible] = 0 if question_type.answer_type == 'none' %>
|
||||
<%= t(:points_possible, "%{points_possible} pts", :points_possible => raw("<span class=\"points question_points\"> / #{hash_get(question, :points_possible, "0")}</span>")) %>
|
||||
<% if hash_get(user_answer, :score_before_regrade) %>
|
||||
<span class=user-regrade-points>
|
||||
<%= t('regraded_score', 'Regraded Score:') %>
|
||||
<%= render_score hash_get(user_answer, :points) %>
|
||||
<%= t(:points_possible, "%{points_possible} pts", :points_possible => raw("<span class=\"points question_points\"> / #{hash_get(question, :points_possible, "0")}</span>")) %>
|
||||
</span>
|
||||
<% end %>
|
||||
</div>
|
||||
<% else %>
|
||||
<%= t(:points_possible, "%{points_possible} pts", :points_possible => raw("<span class=\"points question_points\">#{hash_get(question, :points_possible, "0")}</span>")) %>
|
||||
<% end %>
|
||||
</span>
|
||||
</span>
|
||||
<% if question && hash_get(question, :question_type) != "missing_word_question" && hash_get(question, :question_text) && hash_get(question, :question_text).length < 255 %>
|
||||
<span class='ui-helper-hidden-accessible'><%= hash_get(question, :question_text) %></span>
|
||||
<% else %>
|
||||
|
@ -79,7 +91,19 @@
|
|||
<textarea style="display: none;" name="question_text" class="textarea_question_text"><%= h(hash_get(question, :question_text)) %></textarea>
|
||||
</div>
|
||||
<div id="question_<%= hash_get(question, :id, "new") %>_question_text" class="question_text user_content">
|
||||
<% if user_answer && user_answer[:regrade_option] %>
|
||||
<p class="ui-widget">
|
||||
<div class="ui-state-warning ui-corner-all pad-box-micro text-center">
|
||||
<i class=icon-warning></i>
|
||||
<strong>
|
||||
<%= t('submission_was_regraded',
|
||||
'This question has been regraded. Your score has been updated.') %>
|
||||
</strong>
|
||||
</div>
|
||||
</p>
|
||||
<% end %>
|
||||
<% if question && hash_get(question, :question_type) == "missing_word_question" %>
|
||||
|
||||
<span class="text_before_answers"><%= user_content(hash_get(question, :question_text)) %></span>
|
||||
<select class="answer_select question_input" name="question_<%= hash_get(question, :id, "blank") %>">
|
||||
<option value=""><%= t(:default_question_answer, "[ Choose ]") %></option>
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
<tr class="<%= 'kept' if kept %>">
|
||||
<td>
|
||||
<% if kept %>
|
||||
<%= t 'kept', 'KEPT' %>
|
||||
<% elsif version.version_number == @submission.version_number %>
|
||||
<%= t 'latest', 'LATEST' %>
|
||||
<% end %>
|
||||
</td>
|
||||
<td>
|
||||
<a href="<%= polymorphic_path([@context,@quiz,:history], version: version.version_number)%>">
|
||||
<%= t 'attempt_number', 'Attempt %{att_no}', :att_no => index %>
|
||||
</a>
|
||||
</td>
|
||||
<td>
|
||||
<%= duration_in_minutes((version.finished_at || version.end_at || version.started_at) - version.started_at) %>
|
||||
</td>
|
||||
|
||||
<% if submission_has_regrade?(@submission) && version.score_before_regrade.present? %>
|
||||
<td>
|
||||
<%= score_out_of_points_possible(version.score_before_regrade, params[:preview] ? version.points_possible_at_submission_time : @quiz.points_possible) %>
|
||||
</td>
|
||||
<td class="regraded">
|
||||
<%= score_out_of_points_possible(version.score, params[:preview] ? version.points_possible_at_submission_time : @quiz.points_possible) %>
|
||||
</td>
|
||||
|
||||
<% elsif submission_has_regrade?(@submission) %>
|
||||
<td>
|
||||
<%= score_out_of_points_possible(version.score, params[:preview] ? version.points_possible_at_submission_time : @quiz.points_possible) %>
|
||||
</td>
|
||||
<td class="regraded">-</td>
|
||||
|
||||
<% else %>
|
||||
<td>
|
||||
<%= score_out_of_points_possible(version.score, params[:preview] ? version.points_possible_at_submission_time : @quiz.points_possible) %>
|
||||
</td>
|
||||
<% end %>
|
||||
</tr>
|
|
@ -188,6 +188,18 @@
|
|||
<% end %>
|
||||
|
||||
<header class="quiz-header">
|
||||
<% if submission_has_regrade?(@submission) %>
|
||||
<div class="ui-state-warning ui-corner-all pad-box-micro text-center regraded-warning">
|
||||
<i class=icon-warning></i>
|
||||
<strong>
|
||||
<% if score_affected_by_regrade?(@submission) %>
|
||||
<%= t 'quiz_regraded_your_score_affected', 'This quiz has been re-graded, your score was affected.' %>
|
||||
<% else %>
|
||||
<%= t 'quiz_regraded_your_score_not_affected', 'This quiz has been re-graded, your score was not affected.' %>
|
||||
<% end %>
|
||||
</strong>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<% if @domain_root_account.enable_draft? %>
|
||||
<% if needs_unpublished_warning?(@quiz) %>
|
||||
|
@ -391,6 +403,9 @@
|
|||
<% end %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
|
||||
<div id="quiz-submission-version-table"></div>
|
||||
|
||||
</header>
|
||||
|
||||
<% if @submission && @submission.completed? %>
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
<div class="desc">
|
||||
<%= t 'attempt_history', 'Attempt History' %>
|
||||
</div>
|
||||
<table class="table table-bordered">
|
||||
<thead>
|
||||
<tr>
|
||||
<td> </td>
|
||||
<td><%= t 'attempt', 'Attempt' %></td>
|
||||
<td><%= t 'time', 'Time' %></td>
|
||||
<td><%= t 'score', 'Score' %></td>
|
||||
<% if submission_has_regrade?(@submission) %>
|
||||
<td class="regraded"><%= t 'regraded', 'Re-Graded' %></td>
|
||||
<% end %>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
<% if @versions.size > 1 %>
|
||||
<% version, index = @versions.reverse.each_with_index.detect {|k, v| k.score == @submission.kept_score } %>
|
||||
<% if version %>
|
||||
<%= render :partial => "submission_version", :locals => {:version => version, :index => @versions.length - index, :kept => true} %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
|
||||
<% (@versions.reverse!).each_with_index do |version, index| %>
|
||||
<%= render :partial => "submission_version", :locals => {:version => version, :index => @versions.length - index, :kept => false} %>
|
||||
<% end %>
|
||||
</tbody>
|
||||
</table>
|
|
@ -311,6 +311,7 @@ FakeRails3Routes.draw do
|
|||
match 'quizzes/unpublish' => 'quizzes#unpublish', :as => :quizzes_unpublish
|
||||
resources :quizzes do
|
||||
match 'managed_quiz_data' => 'quizzes#managed_quiz_data', :as => :managed_quiz_data
|
||||
match 'submission_versions' => 'quizzes#submission_versions', :as => :submission_versions
|
||||
match 'reorder' => 'quizzes#reorder', :as => :reorder
|
||||
match 'history' => 'quizzes#history', :as => :history
|
||||
match 'statistics' => 'quizzes#statistics', :as => :statistics
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
class AddScoreBeforeRegradeToQuizSubmission < ActiveRecord::Migration
|
||||
tag :predeploy
|
||||
|
||||
def self.up
|
||||
add_column :quiz_submissions, :score_before_regrade, :float
|
||||
end
|
||||
|
||||
def self.down
|
||||
remove_column :quiz_submissions, :score_before_regrade
|
||||
end
|
||||
end
|
|
@ -0,0 +1,23 @@
|
|||
class CreateQuizRegrades < ActiveRecord::Migration
|
||||
tag :predeploy
|
||||
|
||||
def self.up
|
||||
create_table :quiz_regrades do |t|
|
||||
t.integer :user_id, limit: 8, null: false
|
||||
t.integer :quiz_id, limit: 8, null: false
|
||||
t.integer :quiz_version, null: false
|
||||
|
||||
t.foreign_key :users
|
||||
t.foreign_key :quizzes
|
||||
t.foreign_key :versions
|
||||
|
||||
t.timestamps
|
||||
end
|
||||
|
||||
add_index :quiz_regrades, [:quiz_id, :quiz_version], unique: true
|
||||
end
|
||||
|
||||
def self.down
|
||||
drop_table :quiz_regrades
|
||||
end
|
||||
end
|
|
@ -0,0 +1,21 @@
|
|||
class CreateQuizQuestionRegrades < ActiveRecord::Migration
|
||||
tag :predeploy
|
||||
def self.up
|
||||
create_table :quiz_question_regrades do |t|
|
||||
t.integer :quiz_regrade_id, limit: 8, null: false
|
||||
t.integer :quiz_question_id, limit: 8, null: false
|
||||
t.string :regrade_option, null: false
|
||||
|
||||
t.foreign_key :quiz_regrades
|
||||
t.foreign_key :quiz_questions
|
||||
|
||||
t.timestamps
|
||||
end
|
||||
|
||||
add_index :quiz_question_regrades, [:quiz_regrade_id, :quiz_question_id], unique: true, name: 'index_qqr_on_qr_id_and_qq_id'
|
||||
end
|
||||
|
||||
def self.down
|
||||
drop_table :quiz_question_regrades
|
||||
end
|
||||
end
|
|
@ -0,0 +1,17 @@
|
|||
class CreateQuizRegradeRuns < ActiveRecord::Migration
|
||||
tag :predeploy
|
||||
def self.up
|
||||
create_table :quiz_regrade_runs do |t|
|
||||
t.integer :quiz_regrade_id, limit: 8, null: false
|
||||
t.timestamp :started_at
|
||||
t.timestamp :finished_at
|
||||
t.timestamps
|
||||
|
||||
t.foreign_key :quiz_regrades
|
||||
end
|
||||
end
|
||||
|
||||
def self.down
|
||||
drop_table :quiz_regrade_runs
|
||||
end
|
||||
end
|
|
@ -0,0 +1,16 @@
|
|||
class LoadQuizRegradeFinishedNotification < ActiveRecord::Migration
|
||||
tag :predeploy
|
||||
def self.up
|
||||
return unless Shard.current == Shard.default
|
||||
Canvas::MessageHelper.create_notification({
|
||||
name: 'Quiz Regrade Finished',
|
||||
delay_for: 0,
|
||||
category: 'Grading'
|
||||
})
|
||||
end
|
||||
|
||||
def self.down
|
||||
return unless Shard.current == Shard.default
|
||||
Notification.find_by_name('Quiz Regrade Finished').destroy
|
||||
end
|
||||
end
|
|
@ -0,0 +1,4 @@
|
|||
require 'quiz_regrading/quiz_regrader'
|
||||
require 'quiz_regrading/answer'
|
||||
require 'quiz_regrading/submission'
|
||||
require 'quiz_regrading/attempt_version'
|
|
@ -0,0 +1,120 @@
|
|||
class QuizRegrader::Answer
|
||||
|
||||
REGRADE_OPTIONS = [
|
||||
'full_credit',
|
||||
'current_and_previous_correct',
|
||||
'current_correct_only',
|
||||
'no_regrade'
|
||||
].freeze
|
||||
|
||||
attr_accessor :answer, :question, :regrade_option
|
||||
|
||||
def initialize(answer, question_regrade)
|
||||
@answer = answer
|
||||
@question = question_regrade.quiz_question
|
||||
@regrade_option = question_regrade.regrade_option
|
||||
|
||||
unless REGRADE_OPTIONS.include?(regrade_option)
|
||||
raise ArgumentError.new("Regrade option not valid!")
|
||||
end
|
||||
end
|
||||
|
||||
def regrade!
|
||||
return 0 if regrade_option == 'no_regrade'
|
||||
previous_score = points
|
||||
score = send("mark_#{regrade_option}!")
|
||||
answer[:regrade_option] = regrade_option
|
||||
answer[:score_before_regrade] = previous_score unless points == previous_score
|
||||
answer[:question_id] = question.id
|
||||
score
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def mark_full_credit!
|
||||
return 0 if correct?
|
||||
|
||||
answer[:correct] = true
|
||||
points_possible - points
|
||||
end
|
||||
|
||||
def mark_current_and_previous_correct!
|
||||
return 0 if correct?
|
||||
|
||||
previously_partial = partial?
|
||||
previous_points = points
|
||||
regrade_and_merge_answer!
|
||||
|
||||
# previously partial correct
|
||||
if previously_partial
|
||||
points_possible - previous_points
|
||||
|
||||
# now correct
|
||||
elsif correct?
|
||||
points
|
||||
else
|
||||
0
|
||||
end
|
||||
end
|
||||
|
||||
def mark_current_correct_only!
|
||||
previously_partial = partial?
|
||||
previously_correct = correct?
|
||||
previous_points = points
|
||||
regrade_and_merge_answer!
|
||||
|
||||
# now fully correct
|
||||
if !previously_correct && correct?
|
||||
points_possible - previous_points
|
||||
|
||||
# now partial correct
|
||||
elsif previously_correct && partial?
|
||||
-(points_possible - points)
|
||||
|
||||
# no longer correct
|
||||
elsif previously_correct && !correct?
|
||||
-previous_points
|
||||
else
|
||||
0
|
||||
end
|
||||
end
|
||||
|
||||
def correct?
|
||||
answer[:correct] == true
|
||||
end
|
||||
|
||||
def partial?
|
||||
answer[:correct] == "partial"
|
||||
end
|
||||
|
||||
def points
|
||||
answer[:points] || 0
|
||||
end
|
||||
|
||||
def points_possible
|
||||
question_data[:points_possible] || 0
|
||||
end
|
||||
|
||||
def question_data
|
||||
question.question_data
|
||||
end
|
||||
|
||||
def regrade_and_merge_answer!
|
||||
question_id = question.id
|
||||
|
||||
fake_submission_data = if question_data[:question_type] == 'multiple_answers_question'
|
||||
hash = {}
|
||||
answer.each { |k,v| hash["question_#{question_id}_#{k}"] = v if /answer/ =~ k.to_s }
|
||||
answer.merge(hash)
|
||||
else
|
||||
answer.merge("question_#{question_id}" => answer[:text])
|
||||
end
|
||||
|
||||
question_data.merge!(id: question_id, question_id: question_id)
|
||||
newly_scored_data = QuizSubmission.score_question(question_data, fake_submission_data)
|
||||
|
||||
# clear the answer data and modify it in-place with the newly scored data
|
||||
answer.clear
|
||||
answer.merge!(newly_scored_data)
|
||||
end
|
||||
end
|
|
@ -0,0 +1,17 @@
|
|||
class QuizRegrader::AttemptVersion
|
||||
|
||||
attr_reader :version, :question_regrades
|
||||
|
||||
def initialize(hash)
|
||||
@version = hash.fetch(:version)
|
||||
@question_regrades = hash.fetch(:question_regrades)
|
||||
end
|
||||
|
||||
def regrade!
|
||||
version.model = QuizRegrader::Submission.new(
|
||||
:submission => version.model,
|
||||
:question_regrades => question_regrades).rescored_submission
|
||||
version.save!
|
||||
end
|
||||
|
||||
end
|
|
@ -0,0 +1,42 @@
|
|||
class QuizRegrader
|
||||
|
||||
attr_reader :quiz
|
||||
|
||||
def initialize(quiz, submissions=nil)
|
||||
@quiz = quiz
|
||||
@submissions = submissions
|
||||
end
|
||||
|
||||
def regrade!
|
||||
regrade = quiz.current_regrade
|
||||
return true unless regrade && question_regrades.size > 0
|
||||
|
||||
QuizRegradeRun.perform(regrade) do
|
||||
submissions.each do |submission|
|
||||
QuizRegrader::Submission.new(
|
||||
:submission => submission,
|
||||
:question_regrades => question_regrades).regrade!
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def self.regrade!(quiz, submissions=nil)
|
||||
QuizRegrader.new(quiz, submissions).regrade!
|
||||
end
|
||||
|
||||
def submissions
|
||||
# Using a class level scope here because if a restored "model" from a quiz
|
||||
# version is passed (e.g. during the grade_submission method on quiz
|
||||
# submissions), the association will always be empty.
|
||||
@submissions ||= QuizSubmission.where(quiz_id: quiz.id).select(&:completed?)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# quiz question regrades keyed by question id
|
||||
def question_regrades
|
||||
@questions ||= @quiz.current_quiz_question_regrades.each_with_object({}) do |qr, hash|
|
||||
hash[qr.quiz_question_id] = qr
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,61 @@
|
|||
class QuizRegrader::Submission
|
||||
|
||||
attr_reader :submission, :question_regrades
|
||||
|
||||
def initialize(hash)
|
||||
@submission = hash.fetch(:submission)
|
||||
@question_regrades = hash.fetch(:question_regrades)
|
||||
end
|
||||
|
||||
def regrade!
|
||||
return unless answers_to_grade.size > 0
|
||||
|
||||
# regrade all previous versions
|
||||
submission.attempt_versions.each do |version|
|
||||
QuizRegrader::AttemptVersion.new(
|
||||
:version => version,
|
||||
:question_regrades => question_regrades).regrade!
|
||||
end
|
||||
|
||||
# save this version
|
||||
rescored_submission.save_with_versioning!
|
||||
end
|
||||
|
||||
def rescored_submission
|
||||
previous_score = submission.score_before_regrade || submission.score
|
||||
submission.score += answers_to_grade.map(&:regrade!).inject(&:+)
|
||||
submission.score_before_regrade = previous_score
|
||||
submission.quiz_data = regraded_question_data
|
||||
submission
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def answers_to_grade
|
||||
@answers_to_grade ||= submitted_answers.map do |answer|
|
||||
QuizRegrader::Answer.new(answer, question_regrades[answer[:question_id]])
|
||||
end
|
||||
end
|
||||
|
||||
def submitted_answers
|
||||
@submitted_answers ||= submission.submission_data.select do |answer|
|
||||
question_regrades[answer[:question_id]].present?
|
||||
end
|
||||
end
|
||||
|
||||
def submitted_answer_ids
|
||||
@submitted_answer_ids ||= submitted_answers.map {|q| q[:question_id] }.to_set
|
||||
end
|
||||
|
||||
def regraded_question_data
|
||||
submission.quiz_data.map do |question|
|
||||
id = question[:id]
|
||||
if submitted_answer_ids.include?(id)
|
||||
question.keep_if {|k, v| %w{id position published_at}.include?(k) }
|
||||
question.merge(question_regrades[id].question_data)
|
||||
else
|
||||
question
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -16,6 +16,7 @@
|
|||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
define([
|
||||
'jst/quiz/regrade',
|
||||
'i18n!quizzes',
|
||||
'underscore',
|
||||
'jquery' /* $ */,
|
||||
|
@ -48,8 +49,9 @@ define([
|
|||
'vendor/jquery.scrollTo' /* /\.scrollTo/ */,
|
||||
'jqueryui/sortable' /* /\.sortable/ */,
|
||||
'jqueryui/tabs' /* /\.tabs/ */
|
||||
], function(I18n,_,$,calcCmd, htmlEscape, pluralize, wikiSidebar,
|
||||
DueDateListView, DueDateOverrideView, Quiz, DueDateList,SectionList,
|
||||
], function(regradeTemplate, I18n,_,$,calcCmd, htmlEscape, pluralize,
|
||||
wikiSidebar, DueDateListView, DueDateOverrideView, Quiz,
|
||||
DueDateList,SectionList,
|
||||
MissingDateDialog,MultipleChoiceToggle,TextHelper){
|
||||
|
||||
var dueDateList, overrideView, quizModel, sectionList;
|
||||
|
@ -888,6 +890,9 @@ define([
|
|||
return $answer;
|
||||
}
|
||||
|
||||
var REGRADE_DATA = {};
|
||||
var REGRADE_OPTIONS = ENV.REGRADE_OPTIONS || {};
|
||||
|
||||
function quizData($question) {
|
||||
var $quiz = $("#questions");
|
||||
var quiz = {
|
||||
|
@ -899,7 +904,7 @@ define([
|
|||
$list.each(function(i) {
|
||||
var $question = $(this);
|
||||
var questionData = $question.getTemplateData({
|
||||
textValues: ['question_name', 'question_points', 'question_type', 'answer_selection_type', 'assessment_question_id', 'correct_comments', 'incorrect_comments', 'neutral_comments', 'matching_answer_incorrect_matches', 'equation_combinations', 'equation_formulas'],
|
||||
textValues: ['question_name', 'question_points', 'question_type', 'answer_selection_type', 'assessment_question_id', 'correct_comments', 'incorrect_comments', 'neutral_comments', 'matching_answer_incorrect_matches', 'equation_combinations', 'equation_formulas', 'regrade_option'],
|
||||
htmlValues: ['question_text', 'text_before_answers', 'text_after_answers', 'correct_comments_html', 'incorrect_comments_html', 'neutral_comments_html']
|
||||
});
|
||||
questionData = $.extend(questionData, $question.find(".original_question_text").getFormData());
|
||||
|
@ -1001,6 +1006,7 @@ define([
|
|||
data[id + '[incorrect_comments]'] = question.incorrect_comments;
|
||||
data[id + '[neutral_comments]'] = question.neutral_comments;
|
||||
data[id + '[question_text]'] = question.question_text;
|
||||
data[id + '[regrade_option]'] = question.regrade_option;
|
||||
data[id + '[position]'] = question.position;
|
||||
data[id + '[text_after_answers]'] = question.text_after_answers;
|
||||
data[id + '[matching_answer_incorrect_matches]'] = question.matching_answer_incorrect_matches;
|
||||
|
@ -1438,6 +1444,7 @@ define([
|
|||
$(document).delegate(".edit_question_link", 'click', function(event) {
|
||||
event.preventDefault();
|
||||
var $question = $(this).parents(".question");
|
||||
var questionID = $(this).closest('.question_holder').find('.display_question').attr('id');
|
||||
var question = $question.getTemplateData({
|
||||
textValues: ['question_type', 'correct_comments', 'incorrect_comments', 'neutral_comments', 'question_name', 'question_points', 'answer_selection_type', 'blank_id'],
|
||||
htmlValues: ['question_text', 'correct_comments_html', 'incorrect_comments_html', 'neutral_comments_html']
|
||||
|
@ -1550,6 +1557,15 @@ define([
|
|||
$formQuestion.find(".question_content").triggerHandler('change');
|
||||
$formQuestion.addClass('ready');
|
||||
}, 100);
|
||||
|
||||
// show regrade options if question was changed but quiz not saved
|
||||
var $question = $form.find(".question");
|
||||
var questionID = $form.prev('.display_question').attr('id');
|
||||
var idValue = questionID.replace("question_", "");
|
||||
|
||||
if (REGRADE_OPTIONS[idValue]) {
|
||||
showRegradeOptions($question,questionID);
|
||||
}
|
||||
});
|
||||
|
||||
$(".question_form :input[name='question_type']").change(function() {
|
||||
|
@ -1597,7 +1613,11 @@ define([
|
|||
$(document).delegate(".select_answer_link", 'click', function(event) {
|
||||
event.preventDefault();
|
||||
var $question = $(this).parents(".question");
|
||||
var questionID = $(this).closest('.question_holder').find('.display_question').attr('id');
|
||||
if (!$question.hasClass('selectable')) { return; }
|
||||
if (!REGRADE_DATA[questionID]){
|
||||
REGRADE_DATA[questionID] = correctAnswerIDs($question)
|
||||
}
|
||||
if ($question.find(":input[name='question_type']").val() != "multiple_answers_question") {
|
||||
$question.find(".answer:visible").removeClass('correct_answer')
|
||||
.find('.select_answer_link').attr('title', clickSetCorrect)
|
||||
|
@ -1618,8 +1638,77 @@ define([
|
|||
.find('img').attr('alt', clickSetCorrect);
|
||||
}
|
||||
}
|
||||
|
||||
showRegradeOptions($question,questionID);
|
||||
});
|
||||
|
||||
function showRegradeOptions($el,questionID) {
|
||||
if ($("#student_submissions_warning").length == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
var regradeOptions = $el.find('.regrade-options')
|
||||
if (regradeOptions.length && answersAreTheSameAsBefore($el)) {
|
||||
regradeOptions.remove();
|
||||
enableQuestionForm();
|
||||
return;
|
||||
}
|
||||
if (!regradeOptions.length){
|
||||
questionID = /question_(\d+)/.exec(questionID.toString());
|
||||
var regradeOption = REGRADE_OPTIONS[questionID[1]];
|
||||
|
||||
$el.find('.button-container').before(regradeTemplate({regradeOption: regradeOption}));
|
||||
clickRegradeOptions();
|
||||
}
|
||||
}
|
||||
|
||||
$(document).delegate(".regrade-options", 'click', clickRegradeOptions);
|
||||
|
||||
function clickRegradeOptions(event) {
|
||||
if ($('input[name="regrade_option"]:checked').length === 0) {
|
||||
disableQuestionForm();
|
||||
} else {
|
||||
enableQuestionForm();
|
||||
}
|
||||
}
|
||||
|
||||
function disableQuestionForm() {
|
||||
$('.question_form').find(".submit_button")
|
||||
.attr('disabled', true)
|
||||
.addClass('disabled')
|
||||
.removeClass('button_primary btn-primary');
|
||||
}
|
||||
|
||||
function enableQuestionForm() {
|
||||
$('.question_form').find(".submit_button")
|
||||
.removeClass('disabled')
|
||||
.removeAttr('disabled')
|
||||
.addClass('button_primary btn-primary');
|
||||
}
|
||||
|
||||
function correctAnswerIDs($el){
|
||||
var answers = [];
|
||||
$el.find('.answer').each(function(index) {
|
||||
if ($(this).hasClass('correct_answer')) answers.push(index);
|
||||
});
|
||||
return answers;
|
||||
}
|
||||
|
||||
function answersAreTheSameAsBefore($el) {
|
||||
var questionID = $el.closest('.question_holder').find('.display_question').attr('id');
|
||||
var idValue = questionID.replace("question_", "");
|
||||
|
||||
// we don't know 'old answers' if they've updated and returned
|
||||
if (REGRADE_OPTIONS[idValue]) {
|
||||
return false;
|
||||
|
||||
} else {
|
||||
var oldAnswers = REGRADE_DATA[questionID];
|
||||
var newAnswers = correctAnswerIDs($el);
|
||||
return !_.difference(oldAnswers, newAnswers).length;
|
||||
}
|
||||
}
|
||||
|
||||
$(".question_form :input").change(function() {
|
||||
if ($(this).parents(".answer").length > 0) {
|
||||
var $answer = $(this).parents(".answer");
|
||||
|
@ -2115,8 +2204,9 @@ define([
|
|||
var $question = $(this).find(".question");
|
||||
var answers = [];
|
||||
var questionData = $question.getFormData({
|
||||
values: ['question_type', 'question_name', 'question_points', 'correct_comments', 'incorrect_comments', 'neutral_comments',
|
||||
'question_text', 'answer_selection_type', 'text_after_answers', 'matching_answer_incorrect_matches']
|
||||
textValues: ['question_type', 'question_name', 'question_points', 'correct_comments', 'incorrect_comments', 'neutral_comments',
|
||||
'question_text', 'answer_selection_type', 'text_after_answers', 'matching_answer_incorrect_matches',
|
||||
'regrade_option']
|
||||
});
|
||||
|
||||
// save any open html answers
|
||||
|
@ -2162,6 +2252,7 @@ define([
|
|||
var $answer = $(this);
|
||||
$answer.show();
|
||||
var data = $answer.getFormData();
|
||||
data.id = $answer.find('.id').text();
|
||||
data.blank_id = $answer.find(".blank_id").text();
|
||||
data.answer_text = $answer.find("input[name='answer_text']:visible").val();
|
||||
data.answer_html = $answer.find(".answer_html").html();
|
||||
|
@ -2234,9 +2325,11 @@ define([
|
|||
url = $displayQuestion.find(".update_question_url").attr('href');
|
||||
method = 'PUT';
|
||||
}
|
||||
var oldQuestionData = questionData;
|
||||
var questionData = quizData($displayQuestion);
|
||||
var formData = generateFormQuiz(questionData);
|
||||
var questionData = generateFormQuizQuestion(formData);
|
||||
questionData['question[regrade_option]'] = oldQuestionData.regrade_option;
|
||||
if ($displayQuestion.parent(".question_holder").hasClass('group')) {
|
||||
var $group = quiz.findContainerGroup($displayQuestion.parent(".question_holder"));
|
||||
if ($group) {
|
||||
|
@ -2267,6 +2360,10 @@ define([
|
|||
// after save process completed. Used in quizzes_bundle.coffee
|
||||
$displayQuestion.trigger('saved');
|
||||
$("#unpublished_changes_message").slideDown();
|
||||
if (question) {
|
||||
REGRADE_OPTIONS[question.id] = question.question_data.regrade_option;
|
||||
delete REGRADE_DATA['question_' + question.id];
|
||||
}
|
||||
}, function(data) {
|
||||
$displayQuestion.formErrors(data);
|
||||
});
|
||||
|
|
|
@ -172,9 +172,13 @@ describe QuizzesController do
|
|||
it "should assign variables" do
|
||||
course_with_teacher_logged_in(:active_all => true)
|
||||
course_quiz
|
||||
regrade = @quiz.quiz_regrades.create!(:user_id => @teacher.id, quiz_version: @quiz.version_number)
|
||||
q = @quiz.quiz_questions.create!
|
||||
regrade.quiz_question_regrades.create!(:quiz_question_id => q.id,:regrade_option => 'no_regrade')
|
||||
get 'edit', :course_id => @course.id, :id => @quiz.id
|
||||
assigns[:quiz].should_not be_nil
|
||||
assigns[:quiz].should eql(@quiz)
|
||||
assigns[:js_env][:REGRADE_OPTIONS].should == {q.id => 'no_regrade' }
|
||||
response.should render_template("new")
|
||||
end
|
||||
end
|
||||
|
@ -267,6 +271,19 @@ describe QuizzesController do
|
|||
:display_name => attachment.display_name }
|
||||
}
|
||||
end
|
||||
|
||||
it "assigns js_env for versions if submission is present" do
|
||||
require 'action_controller'
|
||||
require 'action_controller/test_process.rb'
|
||||
course_with_student_logged_in :active_all => true
|
||||
course_quiz !!:active
|
||||
submission = @quiz.generate_submission @user
|
||||
create_attachment_for_file_upload_submission!(submission)
|
||||
get 'show', :course_id => @course.id, :id => @quiz.id
|
||||
|
||||
path = "courses/#{@course.id}/quizzes/#{@quiz.id}/submission_versions"
|
||||
assigns[:js_env][:SUBMISSION_VERSIONS_URL].should include(path)
|
||||
end
|
||||
end
|
||||
|
||||
describe "GET 'managed_quiz_data'" do
|
||||
|
@ -1166,5 +1183,28 @@ describe QuizzesController do
|
|||
@quiz.reload.published?.should be_false
|
||||
end
|
||||
end
|
||||
|
||||
describe "GET submission_versions" do
|
||||
it "requires authorization" do
|
||||
course_with_teacher(:active_all => true)
|
||||
course_quiz
|
||||
get 'submission_versions', :course_id => @course.id, :quiz_id => @quiz.id
|
||||
assert_unauthorized
|
||||
assigns[:quiz].should_not be_nil
|
||||
assigns[:quiz].should eql(@quiz)
|
||||
end
|
||||
|
||||
it "assigns variables" do
|
||||
course_with_teacher_logged_in(:active_all => true)
|
||||
course_quiz
|
||||
submission = @quiz.generate_submission @user
|
||||
create_attachment_for_file_upload_submission!(submission)
|
||||
get 'submission_versions', :course_id => @course.id, :quiz_id => @quiz.id
|
||||
assigns[:quiz].should_not be_nil
|
||||
assigns[:quiz].should eql(@quiz)
|
||||
assigns[:submission].should_not be_nil
|
||||
assigns[:versions].should_not be_nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -0,0 +1,104 @@
|
|||
require 'spec_helper'
|
||||
require 'lib/quiz_regrading'
|
||||
describe "QuizRegrading" do
|
||||
|
||||
def create_quiz_question!(data)
|
||||
question = @quiz.quiz_questions.create!
|
||||
data.merge!(:id => question.id)
|
||||
question.write_attribute(:question_data,data)
|
||||
question.save!
|
||||
question
|
||||
end
|
||||
|
||||
def reset_submission_data!
|
||||
@submission.submission_data = {
|
||||
"question_#{@true_false_question.id}"=> "2",
|
||||
"question_#{@multiple_choice_question.id}" => "4",
|
||||
"question_#{@multiple_answers_question.id}_answer_5" => "1",
|
||||
"question_#{@multiple_answers_question.id}_answer_6" => "0",
|
||||
"question_#{@multiple_answers_question.id}_answer_7" => "0"
|
||||
}.with_indifferent_access
|
||||
@submission.grade_submission
|
||||
@submission.save!
|
||||
end
|
||||
|
||||
def set_regrade_option!(regrade_option)
|
||||
[@ttf_qqr,@maq_qqr,@mcq_qqr].each do |qqr|
|
||||
qqr.regrade_option = regrade_option
|
||||
qqr.save!
|
||||
end
|
||||
reset_submission_data!
|
||||
@quiz.reload
|
||||
end
|
||||
|
||||
before do
|
||||
course_with_student_logged_in(active_all: true)
|
||||
quiz_model(course: @course)
|
||||
@regrade = @quiz.quiz_regrades.find_or_create_by_quiz_id_and_quiz_version(@quiz.id,@quiz.version_number) { |qr| qr.user_id = @student.id }
|
||||
@regrade.should_not be_new_record
|
||||
@true_false_question = create_quiz_question!({
|
||||
:points_possible => 1,
|
||||
:question_type => 'true_false_question',
|
||||
:question_name => 'True/False Question',
|
||||
:answers => [
|
||||
{:text => 'true', :id => 1, :weight => 100},
|
||||
{:text => 'false', :id => 2, :weight => 0}
|
||||
]
|
||||
})
|
||||
@multiple_choice_question = create_quiz_question!({
|
||||
:points_possible => 1,
|
||||
:question_type => 'multiple_choice_question',
|
||||
:question_name => 'Multiple Choice Question',
|
||||
:answers => [
|
||||
{:text => "correct", :id => 3, :weight => 100 },
|
||||
{:text => "nope", :id => 4, :weight => 0}
|
||||
]
|
||||
})
|
||||
@multiple_answers_question = create_quiz_question!({
|
||||
:points_possible => 1,
|
||||
:question_type => 'multiple_answers_question',
|
||||
:question_name => 'Multiple Answers Question',
|
||||
:answers => [
|
||||
{:text => "correct1", :id => 5, :weight => 100},
|
||||
{:text=> "correct2", :id => 6, :weight => 100},
|
||||
{:text => "nope", :id=> 7, :weight => 0 }
|
||||
]
|
||||
})
|
||||
@maq_qqr = @regrade.quiz_question_regrades.create!(quiz_question_id: @multiple_answers_question.id, regrade_option: 'no_regrade')
|
||||
@mcq_qqr = @regrade.quiz_question_regrades.create!(quiz_question_id: @multiple_choice_question.id, regrade_option: 'no_regrade')
|
||||
@ttf_qqr = @regrade.quiz_question_regrades.create!(quiz_question_id: @true_false_question.id, regrade_option: 'no_regrade')
|
||||
@quiz.generate_quiz_data
|
||||
@quiz.workflow_state = 'available'; @quiz.without_versioning { @quiz.save! }
|
||||
@submission = @quiz.generate_submission(@student)
|
||||
reset_submission_data!
|
||||
@submission.save!
|
||||
@submission.score.should == 0.5
|
||||
end
|
||||
|
||||
it 'succesfully regrades the submissions and updates the scores' do
|
||||
set_regrade_option!('full_credit')
|
||||
QuizRegrader.regrade!(@quiz)
|
||||
@submission.reload.score.should == 3
|
||||
|
||||
set_regrade_option!('current_correct_only')
|
||||
data = @true_false_question.question_data
|
||||
data[:answers].first[:weight] = 0
|
||||
data[:answers].second[:weight] = 100
|
||||
@true_false_question.write_attribute(:question_data, data)
|
||||
@true_false_question.save!
|
||||
data = @multiple_choice_question.question_data
|
||||
data[:answers].first[:weight] = 0
|
||||
data[:answers].second[:weight] = 100
|
||||
@multiple_choice_question.write_attribute(:data, data)
|
||||
@multiple_choice_question.save!
|
||||
data = @multiple_answers_question.reload.question_data
|
||||
data[:answers].second[:weight] = 0
|
||||
@multiple_answers_question.write_attribute(:data, data)
|
||||
@multiple_answers_question.save!
|
||||
@quiz.reload
|
||||
|
||||
QuizRegrader.regrade!(@quiz)
|
||||
@submission.reload.score.should == 3
|
||||
end
|
||||
|
||||
end
|
|
@ -0,0 +1,179 @@
|
|||
require 'active_support'
|
||||
require_relative '../../mocha_rspec_adapter'
|
||||
require_relative '../../../lib/quiz_regrading'
|
||||
|
||||
class QuizSubmission; end
|
||||
|
||||
describe QuizRegrader::Answer do
|
||||
|
||||
let(:points) { 15 }
|
||||
|
||||
let(:question) do
|
||||
stub(:id => 1, :question_data => {:id => 1,
|
||||
:regrade_option => 'full_credit',
|
||||
:points_possible => points})
|
||||
end
|
||||
|
||||
let(:question_regrade) do
|
||||
stub(:quiz_question => question,
|
||||
:regrade_option => "full_credit")
|
||||
end
|
||||
|
||||
let(:answer) do
|
||||
{ :question_id => 1, :points => points, :text => ""}
|
||||
end
|
||||
|
||||
let(:wrapper) do
|
||||
QuizRegrader::Answer.new(answer, question_regrade)
|
||||
end
|
||||
|
||||
def mark_original_answer_as!(correct)
|
||||
answer[:correct] = case correct
|
||||
when :correct then true
|
||||
when :wrong then false
|
||||
when :partial then "partial"
|
||||
end
|
||||
end
|
||||
|
||||
def assert_answer_has_regrade_option!(regrade_option)
|
||||
answer[:regrade_option].should == regrade_option
|
||||
end
|
||||
|
||||
def score_question_as!(correct)
|
||||
correct = case correct
|
||||
when :correct then true
|
||||
when :wrong then false
|
||||
when :partial then "partial"
|
||||
end
|
||||
|
||||
sent_params = {}
|
||||
QuizSubmission.expects(:score_question).with do |*args|
|
||||
sent_params, sent_answer_data = args
|
||||
if question.question_data[:question_type] == 'multiple_answers_question'
|
||||
answer.each do |k,v|
|
||||
next unless /answer/ =~ k
|
||||
key = "question_#{question.id}_#{k}"
|
||||
sent_answer_data[key].should == v
|
||||
end
|
||||
else
|
||||
sent_answer_data.should == answer.merge("question_#{question.id}" => answer[:text])
|
||||
end
|
||||
end.returns(sent_params.merge(:points => answer[:points], :correct => correct)).at_least_once
|
||||
end
|
||||
|
||||
describe "#initialize" do
|
||||
|
||||
it 'saves a reference to the passed answer hash' do
|
||||
wrapper.answer.should == answer
|
||||
end
|
||||
|
||||
it 'saves a reference to the passed question hash' do
|
||||
wrapper.question.should == question
|
||||
end
|
||||
|
||||
it 'raises an error if the question has an unrecognized regrade_option' do
|
||||
question_regrade = stub(:quiz_question => question,
|
||||
:regrade_option => "be_a_jerk")
|
||||
|
||||
expect { QuizRegrader::Answer.new(answer, question_regrade) }.to raise_error
|
||||
end
|
||||
|
||||
it 'does not raise an error if question has recognized regrade_option' do
|
||||
question_regrade = stub(:quiz_question => question,
|
||||
:regrade_option => "current_correct_only")
|
||||
|
||||
QuizRegrader::Answer::REGRADE_OPTIONS.each do |regrade_option|
|
||||
expect { QuizRegrader::Answer.new(answer, question_regrade) }.to_not raise_error
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#regrade!' do
|
||||
|
||||
context 'full_credit regrade option' do
|
||||
|
||||
it 'returns the points possible for the question if the answer '+
|
||||
'was not correct before' do
|
||||
mark_original_answer_as!(:wrong)
|
||||
answer[:points] = 0
|
||||
wrapper.regrade!.should == points
|
||||
assert_answer_has_regrade_option!('full_credit')
|
||||
end
|
||||
|
||||
it 'returns 0 if answer was previously correct' do
|
||||
mark_original_answer_as!(:correct)
|
||||
wrapper.regrade!.should == 0
|
||||
assert_answer_has_regrade_option!('full_credit')
|
||||
end
|
||||
end
|
||||
|
||||
context 'current_and_previous_correct regrade option' do
|
||||
|
||||
before { wrapper.regrade_option = 'current_and_previous_correct' }
|
||||
|
||||
it 'returns 0 if previously correct' do
|
||||
mark_original_answer_as!(:correct)
|
||||
wrapper.regrade!.should == 0
|
||||
assert_answer_has_regrade_option!('current_and_previous_correct')
|
||||
end
|
||||
|
||||
it 'returns points possible if previously wrong but now correct' do
|
||||
mark_original_answer_as!(:wrong)
|
||||
score_question_as!(:correct)
|
||||
wrapper.regrade!.should == points
|
||||
assert_answer_has_regrade_option!('current_and_previous_correct')
|
||||
end
|
||||
|
||||
it 'returns points possible - previous score if previously partial correct' do
|
||||
previous_score = answer[:points]
|
||||
mark_original_answer_as!(:partial)
|
||||
score_question_as!(:correct)
|
||||
wrapper.regrade!.should == points - previous_score
|
||||
assert_answer_has_regrade_option!('current_and_previous_correct')
|
||||
end
|
||||
|
||||
it 'returns 0 if previously wrong and wrong now' do
|
||||
mark_original_answer_as!(:wrong)
|
||||
score_question_as!(:wrong)
|
||||
wrapper.regrade!.should == 0
|
||||
assert_answer_has_regrade_option!('current_and_previous_correct')
|
||||
end
|
||||
end
|
||||
|
||||
context 'current_correct_only regrade option' do
|
||||
|
||||
before { wrapper.regrade_option = 'current_correct_only' }
|
||||
|
||||
it 'returns points_possible - points if previously wrong but now correct' do
|
||||
mark_original_answer_as!(:wrong)
|
||||
score_question_as!(:correct)
|
||||
wrapper.regrade!.should == 0
|
||||
assert_answer_has_regrade_option!('current_correct_only')
|
||||
end
|
||||
|
||||
it 'returns 0 if previously correct and correct after regrading' do
|
||||
mark_original_answer_as!(:correct)
|
||||
score_question_as!(:correct)
|
||||
wrapper.regrade!.should == 0
|
||||
assert_answer_has_regrade_option!('current_correct_only')
|
||||
end
|
||||
|
||||
it 'returns -points if prev correct but wrong after regrading' do
|
||||
mark_original_answer_as!(:correct)
|
||||
score_question_as!(:wrong)
|
||||
wrapper.regrade!.should == -points
|
||||
assert_answer_has_regrade_option!('current_correct_only')
|
||||
end
|
||||
|
||||
it 'works with multiple_answer_questions' do
|
||||
question.question_data.merge!(:question_type => 'multiple_answers_question')
|
||||
answer.merge!(:answer_1 => "0", :answer_2 => "1")
|
||||
mark_original_answer_as!(:correct)
|
||||
score_question_as!(:correct)
|
||||
wrapper.regrade!.should == 0
|
||||
assert_answer_has_regrade_option!('current_correct_only')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
|
@ -0,0 +1,77 @@
|
|||
require 'active_support'
|
||||
require_relative '../../mocha_rspec_adapter'
|
||||
require_relative '../../../lib/quiz_regrading'
|
||||
|
||||
describe QuizRegrader::AttemptVersion do
|
||||
|
||||
let(:regrade_options) do
|
||||
{1 => 'no_regrade', 2 => 'full_credit', 3 => 'current_correct_only' }
|
||||
end
|
||||
|
||||
let(:question_regrades) do
|
||||
1.upto(3).each_with_object({}) do |i, hash|
|
||||
hash[i] = stub(:quiz_question => stub(:id => i, :question_data => {:id => i}),
|
||||
:question_data => {:id => i},
|
||||
:regrade_option => regrade_options[i])
|
||||
end
|
||||
end
|
||||
|
||||
let(:quiz_data) do
|
||||
question_regrades.map {|id, q| q.quiz_question.question_data.dup }
|
||||
end
|
||||
|
||||
let(:submission_data) do
|
||||
1.upto(3).map {|i| {:question_id => i} }
|
||||
end
|
||||
|
||||
let(:submission) do
|
||||
stub(:score => 0,
|
||||
:quiz_data => quiz_data,
|
||||
:submission_data => submission_data,
|
||||
:write_attribute => {})
|
||||
end
|
||||
|
||||
let(:version) do
|
||||
stub(:model => submission)
|
||||
end
|
||||
|
||||
let(:attempt_version) do
|
||||
QuizRegrader::AttemptVersion.new(:version => version,
|
||||
:question_regrades => question_regrades)
|
||||
end
|
||||
|
||||
describe "#initialize" do
|
||||
it "saves a reference to the passed version" do
|
||||
attempt_version.version.should == version
|
||||
end
|
||||
|
||||
it "saves a reference to the passed regrade quiz questions" do
|
||||
attempt_version.question_regrades.should == question_regrades
|
||||
end
|
||||
end
|
||||
|
||||
describe "#regrade!" do
|
||||
|
||||
it "assigns the model and saves the version" do
|
||||
submission_data.each do |answer|
|
||||
answer_stub = stub
|
||||
answer_stub.expects(:regrade!).once.returns(1)
|
||||
QuizRegrader::Answer.expects(:new).returns answer_stub
|
||||
end
|
||||
|
||||
# submission data isn't called if not included in question_regrades
|
||||
submission_data << {:question_id => 4}
|
||||
QuizRegrader::Answer.expects(:new).with(submission_data.last, nil).never
|
||||
|
||||
submission.expects(:score=).with(3)
|
||||
submission.expects(:score_before_regrade).returns nil
|
||||
submission.expects(:score_before_regrade=).with(0)
|
||||
submission.expects(:quiz_data=).with(question_regrades.map { |id, q| q.question_data })
|
||||
|
||||
version.expects(:model=)
|
||||
version.expects(:save!)
|
||||
|
||||
attempt_version.regrade!
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,73 @@
|
|||
require 'spec_helper'
|
||||
require_relative '../../../lib/quiz_regrading'
|
||||
|
||||
describe QuizRegrader do
|
||||
|
||||
before { Timecop.freeze(Time.local(2013)) }
|
||||
after { Timecop.return }
|
||||
|
||||
let(:questions) do
|
||||
1.upto(4).map do |i|
|
||||
stub(:id => i, :question_data => { :id => i, :regrade_option => 'full_credit'})
|
||||
end
|
||||
end
|
||||
|
||||
let(:submissions) do
|
||||
1.upto(4).map {|i| stub(:id => i, :completed? => true) }
|
||||
end
|
||||
|
||||
let(:current_quiz_question_regrades) do
|
||||
1.upto(4).map { |i| stub(:quiz_question_id => i, :regrade_option => 'full_credit') }
|
||||
end
|
||||
|
||||
let(:quiz) { stub(:quiz_questions => questions,
|
||||
:id => 1,
|
||||
:version_number => 1,
|
||||
:current_quiz_question_regrades => current_quiz_question_regrades,
|
||||
:quiz_submissions => submissions) }
|
||||
|
||||
let(:quiz_regrade) { stub(:id => 1, :quiz => quiz) }
|
||||
|
||||
before do
|
||||
quiz.stubs(:current_regrade).returns quiz_regrade
|
||||
QuizQuestion.stubs(:where).with(quiz_id: quiz.id).returns questions
|
||||
QuizSubmission.stubs(:where).with(quiz_id: quiz.id).returns submissions
|
||||
end
|
||||
|
||||
let(:quiz_regrader) { QuizRegrader.new(quiz) }
|
||||
|
||||
describe '#initialize' do
|
||||
it 'saves the quiz passed' do
|
||||
quiz_regrader.quiz.should == quiz
|
||||
end
|
||||
|
||||
it 'takes an optional submissions argument' do
|
||||
submissions = []
|
||||
QuizRegrader.new(quiz,submissions).submissions.should == submissions
|
||||
end
|
||||
end
|
||||
|
||||
describe "#submissions" do
|
||||
it 'should skip submissions that are in progress' do
|
||||
questions << stub(:id => 5, :question_data => {:regrade_option => 'no_regrade'})
|
||||
|
||||
uncompleted_submission = stub(:id => 5, :completed? => false)
|
||||
submissions << uncompleted_submission
|
||||
|
||||
quiz_regrader.submissions.length.should == 4
|
||||
quiz_regrader.submissions.detect {|s| s.id == 5 }.should be_nil
|
||||
end
|
||||
end
|
||||
|
||||
describe '#regrade!' do
|
||||
it 'creates a QuizRegrader::Submission for each submission and regrades them' do
|
||||
questions << stub(:id => 5, :question_data => {:regrade_option => 'no_regrade'})
|
||||
questions << stub(:id => 6, :question_data => {} )
|
||||
|
||||
QuizRegradeRun.expects(:perform).with(quiz_regrade)
|
||||
QuizRegrader::Submission.any_instance.stubs(:regrade!)
|
||||
|
||||
quiz_regrader.regrade!
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,74 @@
|
|||
require 'active_support'
|
||||
require_relative '../../mocha_rspec_adapter'
|
||||
require_relative '../../../lib/quiz_regrading'
|
||||
|
||||
describe QuizRegrader::Submission do
|
||||
|
||||
let(:regrade_options) do
|
||||
{1 => 'no_regrade', 2 => 'full_credit', 3 => 'current_correct_only' }
|
||||
end
|
||||
|
||||
let(:question_regrades) do
|
||||
1.upto(3).each_with_object({}) do |i, hash|
|
||||
hash[i] = stub(:quiz_question => stub(:id => i, :question_data => {:id => i}),
|
||||
:question_data => {:id => i},
|
||||
:regrade_option => regrade_options[i])
|
||||
end
|
||||
end
|
||||
|
||||
let(:quiz_data) do
|
||||
question_regrades.map {|id, q| q.quiz_question.question_data.dup }
|
||||
end
|
||||
|
||||
let(:submission_data) do
|
||||
1.upto(3).map {|i| {:question_id => i} }
|
||||
end
|
||||
|
||||
let(:submission) do
|
||||
stub(:score => 0,
|
||||
:quiz_data => quiz_data,
|
||||
:submission_data => submission_data,
|
||||
:write_attribute => {})
|
||||
end
|
||||
|
||||
let(:wrapper) do
|
||||
QuizRegrader::Submission.new(:submission => submission,
|
||||
:question_regrades => question_regrades)
|
||||
end
|
||||
|
||||
|
||||
describe "#initialize" do
|
||||
it "saves a reference to the passed submission" do
|
||||
wrapper.submission.should == submission
|
||||
end
|
||||
|
||||
it "saves a reference to the passed regrade quiz questions" do
|
||||
wrapper.question_regrades.should == question_regrades
|
||||
end
|
||||
end
|
||||
|
||||
describe "#regrade!" do
|
||||
it "wraps each answer in the submisison's submission_data and regrades" do
|
||||
submission_data.each do |answer|
|
||||
answer_stub = stub
|
||||
answer_stub.expects(:regrade!).once.returns(1)
|
||||
QuizRegrader::Answer.expects(:new).returns answer_stub
|
||||
end
|
||||
|
||||
# submission data isn't called if not included in question_regrades
|
||||
submission_data << {:question_id => 4}
|
||||
QuizRegrader::Answer.expects(:new).with(submission_data.last, nil).never
|
||||
|
||||
# submission updates and saves correct data
|
||||
submission.expects(:save_with_versioning!).once
|
||||
submission.expects(:score=).with(3)
|
||||
submission.expects(:score_before_regrade).returns nil
|
||||
submission.expects(:score_before_regrade=).with(0)
|
||||
submission.expects(:quiz_data=).with(question_regrades.map { |id, q| q.question_data })
|
||||
submission.expects(:attempt_versions).returns []
|
||||
|
||||
wrapper.regrade!
|
||||
end
|
||||
end
|
||||
|
||||
end
|
|
@ -687,6 +687,7 @@ describe ContextModule do
|
|||
|
||||
# the quiz keeps the highest score; should still be unlocked
|
||||
@submission.score = 50
|
||||
@submission.attempt = 2
|
||||
@submission.with_versioning(&:save)
|
||||
@submission.kept_score.should == 100
|
||||
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper.rb')
|
||||
|
||||
describe QuizQuestionRegrade do
|
||||
|
||||
describe "relationships" do
|
||||
|
||||
it "belongs to a quiz_question" do
|
||||
QuizQuestionRegrade.new.should respond_to :quiz_question
|
||||
end
|
||||
|
||||
it "belongs to a quiz_regrade" do
|
||||
QuizQuestionRegrade.new.should respond_to :quiz_regrade
|
||||
end
|
||||
end
|
||||
|
||||
describe "validations" do
|
||||
|
||||
it "validates the presence of quiz_question_id & quiz_regrade_id" do
|
||||
QuizQuestionRegrade.new.should_not be_valid
|
||||
QuizQuestionRegrade.new(quiz_question_id: 1, quiz_regrade_id: 1).should be_valid
|
||||
end
|
||||
end
|
||||
|
||||
describe "#question_data" do
|
||||
it "should delegate to quiz question" do
|
||||
question = QuizQuestion.new
|
||||
question.stubs(:question_data => "foo")
|
||||
|
||||
qq_regrade = QuizQuestionRegrade.new
|
||||
qq_regrade.quiz_question = question
|
||||
qq_regrade.question_data.should == "foo"
|
||||
end
|
||||
end
|
||||
end
|
|
@ -38,6 +38,36 @@ describe QuizQuestion do
|
|||
data[:answers][1][:weight].should eql(0.0)
|
||||
end
|
||||
|
||||
describe "#question_data=" do
|
||||
before do
|
||||
course_with_teacher
|
||||
|
||||
@quiz = @course.quizzes.create
|
||||
|
||||
@data = {:question_name => 'test question',
|
||||
:points_possible => '1',
|
||||
:question_type => 'multiple_choice_question',
|
||||
:answers => {'answer_0' => {'answer_text' => '1', 'id' => 1},
|
||||
'answer_1' => {'answer_text' => '2', 'id' => 2},
|
||||
'answer_1' => {'answer_text' => '3', 'id' => 3},
|
||||
'answer_1' => {'answer_text' => '4', 'id' => 4}}}
|
||||
@question = @quiz.quiz_questions.create(:question_data => @data)
|
||||
end
|
||||
|
||||
it "should save regrade if passed in regrade option in data hash" do
|
||||
QuizQuestionRegrade.first.should be_nil
|
||||
|
||||
QuizRegrade.create(quiz_id: @quiz.id, user_id: @user.id, quiz_version: @quiz.version_number)
|
||||
@question.question_data = @data.merge(:regrade_option => 'full_credit',
|
||||
:regrade_user => @user)
|
||||
@question.save
|
||||
|
||||
question_regrade = QuizQuestionRegrade.first
|
||||
question_regrade.should be
|
||||
question_regrade.regrade_option.should == 'full_credit'
|
||||
end
|
||||
end
|
||||
|
||||
context "migrate_question_hash" do
|
||||
before do
|
||||
course_with_teacher
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe QuizRegradeRun do
|
||||
|
||||
it "validates presence of quiz_regrade_id" do
|
||||
QuizRegradeRun.new(quiz_regrade_id: 1).should be_valid
|
||||
QuizRegradeRun.new(quiz_regrade_id: nil).should_not be_valid
|
||||
end
|
||||
|
||||
describe "#perform" do
|
||||
before(:each) do
|
||||
@course = Course.create!
|
||||
@quiz = Quiz.create!(:context => @course)
|
||||
@user = User.create!
|
||||
|
||||
@regrade = QuizRegrade.create(:user_id => @user.id, :quiz_id => @quiz.id, :quiz_version => 1)
|
||||
end
|
||||
|
||||
it "creates a new quiz regrade run" do
|
||||
QuizRegradeRun.first.should be_nil
|
||||
|
||||
QuizRegradeRun.perform(@regrade) do
|
||||
# noop
|
||||
end
|
||||
|
||||
run = QuizRegradeRun.first
|
||||
run.started_at.should_not be_nil
|
||||
run.finished_at.should_not be_nil
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,44 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe QuizRegrade do
|
||||
|
||||
before { Timecop.freeze(Time.local(2013)) }
|
||||
after { Timecop.return }
|
||||
|
||||
def quiz_regrade
|
||||
QuizRegrade.new(quiz_id: 1, user_id: 1, quiz_version: 1)
|
||||
end
|
||||
|
||||
describe "relationships" do
|
||||
|
||||
it "belongs to a quiz" do
|
||||
QuizRegrade.new.should respond_to :quiz
|
||||
end
|
||||
|
||||
it "belongs to a user" do
|
||||
QuizRegrade.new.should respond_to :user
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
describe "validations" do
|
||||
it "validates presence of quiz_id" do
|
||||
QuizRegrade.new(quiz_id: nil).should_not be_valid
|
||||
end
|
||||
|
||||
it "validates presence of user id" do
|
||||
QuizRegrade.new(quiz_id: 1,user_id: nil).should_not be_valid
|
||||
end
|
||||
|
||||
it "validates presence of quiz_version" do
|
||||
QuizRegrade.new(quiz_id: 1, user_id: 1, quiz_version: nil).
|
||||
should_not be_valid
|
||||
end
|
||||
|
||||
it "is valid when all required attributes are present" do
|
||||
QuizRegrade.new(quiz_id: 1, user_id: 1, quiz_version: 1).
|
||||
should be_valid
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -1104,4 +1104,26 @@ describe Quiz do
|
|||
@quiz.should_not be_published
|
||||
end
|
||||
end
|
||||
|
||||
context "#current_regrade" do
|
||||
|
||||
before { @quiz = @course.quizzes.create! title: 'Test Quiz' }
|
||||
|
||||
it "returns the regrade for the quiz and quiz version" do
|
||||
regrade = QuizRegrade.find_or_create_by_quiz_id_and_quiz_version(@quiz.id,@quiz.version_number) { |qr| qr.user_id = 1 }
|
||||
@quiz.current_regrade.should == regrade
|
||||
end
|
||||
end
|
||||
|
||||
context "#current_regrade_question_ids" do
|
||||
|
||||
before { @quiz = @course.quizzes.create! title: 'Test Quiz' }
|
||||
|
||||
it "returns the correct question ids" do
|
||||
q = @quiz.quiz_questions.create!
|
||||
regrade = QuizRegrade.find_or_create_by_quiz_id_and_quiz_version(@quiz.id,@quiz.version_number) { |qr| qr.user_id = 1 }
|
||||
rq = regrade.quiz_question_regrades.create! quiz_question_id: q.id, regrade_option: 'current_correct_only'
|
||||
@quiz.current_quiz_question_regrades.should == [rq]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -332,6 +332,34 @@ describe QuizSubmission do
|
|||
s.kept_score.should eql(6.0)
|
||||
end
|
||||
|
||||
it "should calculate highest score based on most recent version of an attempt" do
|
||||
q = @course.quizzes.create!(:scoring_policy => "keep_highest")
|
||||
s = q.quiz_submissions.new
|
||||
|
||||
s.workflow_state = "complete"
|
||||
s.score = 5.0
|
||||
s.attempt = 1
|
||||
s.with_versioning(true, &:save!)
|
||||
s.version_number.should eql(1)
|
||||
s.score.should eql(5.0)
|
||||
s.kept_score.should eql(5.0)
|
||||
|
||||
# regrade
|
||||
s.score_before_regrade = 5.0
|
||||
s.score = 4.0
|
||||
s.attempt = 1
|
||||
s.with_versioning(true, &:save!)
|
||||
s.version_number.should eql(2)
|
||||
s.kept_score.should eql(4.0)
|
||||
|
||||
# new attempt
|
||||
s.score = 3.0
|
||||
s.attempt = 2
|
||||
s.with_versioning(true, &:save!)
|
||||
s.version_number.should eql(3)
|
||||
s.kept_score.should eql(4.0)
|
||||
end
|
||||
|
||||
describe "with an essay question" do
|
||||
before(:each) do
|
||||
quiz_with_graded_submission([{:question_data => {:name => 'question 1', :points_possible => 1, 'question_type' => 'essay_question'}}]) do
|
||||
|
@ -1460,6 +1488,80 @@ describe QuizSubmission do
|
|||
|
||||
end
|
||||
|
||||
describe "submitted_versions" do
|
||||
let(:submission) { @quiz.quiz_submissions.build }
|
||||
|
||||
before do
|
||||
submission.grade_submission
|
||||
end
|
||||
|
||||
it "should find regrade versions for a submission" do
|
||||
submission.submitted_versions.length.should == 1
|
||||
end
|
||||
end
|
||||
|
||||
describe "attempt_versions" do
|
||||
let(:quiz) { @course.quizzes.create! }
|
||||
let(:submission) { quiz.quiz_submissions.new }
|
||||
|
||||
it "should find attempt versions for a submission" do
|
||||
submission.workflow_state = "complete"
|
||||
submission.score = 5.0
|
||||
submission.attempt = 1
|
||||
submission.with_versioning(true, &:save!)
|
||||
submission.version_number.should eql(1)
|
||||
submission.score.should eql(5.0)
|
||||
|
||||
# regrade
|
||||
submission.score_before_regrade = 5.0
|
||||
submission.score = 4.0
|
||||
submission.attempt = 1
|
||||
submission.with_versioning(true, &:save!)
|
||||
submission.version_number.should eql(2)
|
||||
|
||||
# new attempt
|
||||
submission.score = 3.0
|
||||
submission.attempt = 2
|
||||
submission.with_versioning(true, &:save!)
|
||||
submission.version_number.should eql(3)
|
||||
|
||||
attempt_versions = submission.attempt_versions
|
||||
attempt_versions.length.should == 2
|
||||
attempt_versions.first.should be_a(Version)
|
||||
end
|
||||
end
|
||||
|
||||
describe "submitted_attempts" do
|
||||
let(:quiz) { @course.quizzes.create! }
|
||||
let(:submission) { quiz.quiz_submissions.new }
|
||||
|
||||
it "should find attempt versions for a submission" do
|
||||
submission.workflow_state = "complete"
|
||||
submission.score = 5.0
|
||||
submission.attempt = 1
|
||||
submission.with_versioning(true, &:save!)
|
||||
submission.version_number.should eql(1)
|
||||
submission.score.should eql(5.0)
|
||||
|
||||
# regrade
|
||||
submission.score_before_regrade = 5.0
|
||||
submission.score = 4.0
|
||||
submission.attempt = 1
|
||||
submission.with_versioning(true, &:save!)
|
||||
submission.version_number.should eql(2)
|
||||
|
||||
# new attempt
|
||||
submission.score = 3.0
|
||||
submission.attempt = 2
|
||||
submission.with_versioning(true, &:save!)
|
||||
submission.version_number.should eql(3)
|
||||
|
||||
submitted_attempts = submission.submitted_attempts
|
||||
submitted_attempts.length.should == 2
|
||||
submitted_attempts.first.should be_a(QuizSubmission)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'broadcast policy' do
|
||||
before do
|
||||
Notification.create(:name => 'Submission Graded')
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
#
|
||||
# Copyright (C) 2011 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')
|
||||
require File.expand_path(File.dirname(__FILE__) + '/../views_helper')
|
||||
|
||||
describe "/quizzes/submission_versions" do
|
||||
it "should render" do
|
||||
course_with_teacher(:active_all => true)
|
||||
course_quiz
|
||||
|
||||
view_context
|
||||
ActiveRecord::Base.clear_cached_contexts
|
||||
assigns[:quiz] = @quiz
|
||||
assigns[:versions] = []
|
||||
|
||||
render "quizzes/submission_versions"
|
||||
response.should_not be_nil
|
||||
end
|
||||
end
|
|
@ -30,6 +30,12 @@ class Version < ActiveRecord::Base #:nodoc:
|
|||
obj.send("force_version_number", self.number)
|
||||
obj
|
||||
end
|
||||
|
||||
# INSTRUCTURE: Added to allow previous version models to be updated
|
||||
def model=(model)
|
||||
options = model.class.simply_versioned_options
|
||||
self.yaml = model.attributes.except(*options[:exclude]).to_yaml
|
||||
end
|
||||
|
||||
# Return the next higher numbered version, or nil if this is the last version
|
||||
def next
|
||||
|
|
|
@ -80,6 +80,31 @@ describe 'simply_versioned' do
|
|||
end
|
||||
end
|
||||
|
||||
describe "#model=" do
|
||||
let(:woozel) { Woozel.create!(:name => 'Eeyore') }
|
||||
|
||||
it "should assign the model for the version" do
|
||||
woozel.versions.length.should eql(1)
|
||||
woozel.versions.current.model.name.should eql('Eeyore')
|
||||
|
||||
woozel.name = 'Piglet'
|
||||
woozel.with_versioning(:explicit => true, &:save!)
|
||||
|
||||
woozel.versions.length.should eql(2)
|
||||
|
||||
first_version = woozel.versions.first
|
||||
first_model = first_version.model
|
||||
first_model.name.should eql('Eeyore')
|
||||
|
||||
first_model.name = 'Foo'
|
||||
first_version.model = first_model
|
||||
first_version.save!
|
||||
|
||||
versions = woozel.reload.versions
|
||||
versions.first.model.name.should eql('Foo')
|
||||
end
|
||||
end
|
||||
|
||||
describe "#current_version?" do
|
||||
before do
|
||||
@woozel = Woozel.create! name: 'test'
|
||||
|
|
Loading…
Reference in New Issue