@@ -42,16 +42,6 @@ class Quizzes::Quiz < ActiveRecord::Base
4242 attr_readonly :context_id , :context_type
4343 attr_accessor :notify_of_update
4444
45- # @property [Fixnum] submission_question_index
46- # @private
47- #
48- # A counter used in generating question names for students based on the
49- # position of the question within the quiz_data set.
50- #
51- # See #generate_submission
52- # See #generate_submission_question
53- attr_readonly :submission_question_index
54-
5545 has_many :quiz_questions , :dependent => :destroy , :order => 'position' , class_name : 'Quizzes::QuizQuestion'
5646 has_many :quiz_submissions , :dependent => :destroy , :class_name => 'Quizzes::QuizSubmission'
5747 has_many :quiz_groups , :dependent => :destroy , :order => 'position' , class_name : 'Quizzes::QuizGroup'
@@ -545,7 +535,7 @@ def question_count(force_check=false)
545535 if q [ :pick_count ]
546536 question_count += q [ :actual_pick_count ] || q [ :pick_count ]
547537 else
548- question_count += 1 unless q [ :question_type ] == Quizzes ::QuizQuestion ::TEXT_ONLY
538+ question_count += 1 unless q [ :question_type ] == Quizzes ::QuizQuestion ::Q_TEXT_ONLY
549539 end
550540 end
551541 question_count || 0
@@ -559,36 +549,22 @@ def available_question_count
559549 # the version found by gathering relationships on the Quiz data models,
560550 # but the version being held in Quizzes::Quiz.quiz_data. Caches the result
561551 # in @stored_questions.
562- def stored_questions ( hashes = nil )
563- res = [ ]
564- return @stored_questions if @stored_questions && !hashes
565- questions = hashes || self . quiz_data || [ ]
566- questions . each do |val |
567-
568- if val [ :answers ]
569- val [ :answers ] = prepare_answers ( val )
570- val [ :matches ] = prepare_matches ( val ) if val [ :matches ]
571- elsif val [ :questions ] # It's a Quizzes::QuizGroup
572- if val [ :assessment_question_bank_id ]
573- # It points to a question bank
574- # question/answer/match shuffling happens when a submission is generated
575- else #normal Quizzes::QuizGroup
576- questions = [ ]
577- val [ :questions ] . each do |question |
578- if question [ :answers ]
579- question [ :answers ] = prepare_answers ( question )
580- question [ :matches ] = prepare_matches ( question ) if question [ :matches ]
581- end
582- questions << question
583- end
584- questions = questions . sort_by { |q | rand }
585- val [ :questions ] = questions
586- end
552+ def stored_questions ( preview = false )
553+ return @stored_questions if @stored_questions && !preview
554+
555+ @stored_questions = begin
556+ data_set = if preview
557+ self . generate_quiz_data ( :persist => false )
558+ else
559+ self . quiz_data || [ ]
587560 end
588- res << val
561+
562+ builder = Quizzes ::QuizQuestionBuilder . new ( {
563+ shuffle_answers : self . shuffle_answers
564+ } )
565+
566+ builder . shuffle_quiz_data! ( data_set )
589567 end
590- @stored_questions = res
591- res
592568 end
593569
594570 def single_attempt?
@@ -599,121 +575,6 @@ def unlimited_attempts?
599575 self . allowed_attempts == -1
600576 end
601577
602- def generate_submission_question ( q )
603- @submission_question_index = 0 if @submission_question_index . nil?
604-
605- unless q [ :question_type ] == Quizzes ::QuizQuestion ::TEXT_ONLY
606- @submission_question_index += 1
607- end
608-
609- self . class . decorate_question_for_submission ( q , @submission_question_index )
610- end
611-
612- # TODO: could this stop mutating the question object and instead return the
613- # decorated version?
614- #
615- # this currently has too many side-effects: on quiz_data, @stored_questions,
616- # and (what we really want) a submissions's quiz_data...
617- #
618- # anyway if ur wondering why any of these fields are being modified in a spec
619- # when you are just innocently calling Quiz#generate_submission, you know why
620- def self . decorate_question_for_submission ( q , position )
621- q [ :name ] = t '#quizzes.quiz.question_name_counter' , "Question %{question_number}" , :question_number => position
622-
623- if q [ :question_type ] == Quizzes ::QuizQuestion ::TEXT_ONLY
624- q [ :name ] = t '#quizzes.quiz.default_text_only_question_name' , "Spacer"
625- elsif q [ :question_type ] == 'fill_in_multiple_blanks_question'
626- text = q [ :question_text ]
627- variables = q [ :answers ] . map { |a | a [ :blank_id ] } . uniq
628- variables . each do |variable |
629- variable_id = ::AssessmentQuestion . variable_id ( variable )
630- re = Regexp . new ( "\\ [#{ variable } \\ ]" )
631- text = text . sub ( re , "<input class='question_input' type='text' autocomplete='off' style='width: 120px;' name='question_#{ q [ :id ] } _#{ variable_id } ' value='{{question_#{ q [ :id ] } _#{ variable_id } }}' />" )
632- end
633- q [ :original_question_text ] = q [ :question_text ]
634- q [ :question_text ] = text
10BC8
635- elsif q [ :question_type ] == 'multiple_dropdowns_question'
636- text = q [ :question_text ]
637- variables = q [ :answers ] . map { |a | a [ :blank_id ] } . uniq
638- variables . each do |variable |
639- variable_id = ::AssessmentQuestion . variable_id ( variable )
640- variable_answers = q [ :answers ] . select { |a | a [ :blank_id ] == variable }
641- options = variable_answers . map { |a | "<option value='#{ a [ :id ] } '>#{ CGI ::escapeHTML ( a [ :text ] ) } </option>" }
642- select = "<select class='question_input' name='question_#{ q [ :id ] } _#{ variable_id } '><option value=''>#{ ERB ::Util . h ( t ( '#quizzes.quiz.default_question_input' , "[ Select ]" ) ) } </option>#{ options } </select>"
643- re = Regexp . new ( "\\ [#{ variable } \\ ]" )
644- text = text . sub ( re , select )
645- end
646- q [ :original_question_text ] = q [ :question_text ]
647- q [ :question_text ] = text
648- # on equation questions, pick one of the formulas, plug it in
649- # and you should be able to treat it like a numerical_answer
650- # question for all intents and purposes
651- elsif q [ :question_type ] == 'calculated_question'
652- text = q [ :question_text ]
653- q [ :answers ] = [ q [ :answers ] . sort_by { |a | rand } . first ] . compact
654- if q [ :answers ] . first
655- q [ :answers ] . first [ :variables ] . each do |variable |
656- re = Regexp . new ( "\\ [#{ variable [ :name ] } \\ ]" )
657- text = text . gsub ( re , variable [ :value ] . to_s )
658- end
659- end
660- q [ :question_text ] = text
661- end
662- q [ :question_name ] = q [ :name ]
663- q
664- end
665-
666- def build_user_questions ( preview )
667- user_questions = [ ]
668- @submission_question_index = 0
669- @stored_questions = nil
670- submission_questions = self . stored_questions
671- if preview
672- submission_questions = self . stored_questions ( generate_quiz_data ( :persist => false ) )
673- end
674-
675- exclude_ids = submission_questions . map { |q | q [ :assessment_question_id ] } . compact
676- submission_questions . each do |q |
677- # pulling from question group
678- if q [ :pick_count ]
679-
680- # pulling from question bank
681- if q [ :assessment_question_bank_id ]
682- bank = ::AssessmentQuestionBank . where ( id : q [ :assessment_question_bank_id ] ) . first if q [ :assessment_question_bank_id ] . present?
683- if bank
684- questions = bank . select_for_submission ( q [ :pick_count ] , exclude_ids )
685- exclude_ids . concat ( questions . map { |q | q . id } )
686-
687- questions = questions . map { |aq | aq . data }
688- questions . each do |question |
689- if question [ :answers ]
690- question [ :answers ] = prepare_answers ( question )
691- question [ :matches ] = prepare_matches ( question ) if question [ :matches ]
692- end
693- question [ :points_possible ] = q [ :question_points ]
694- question [ :published_at ] = q [ :published_at ]
695- user_questions << generate_submission_question ( question )
696- end
697- end
698-
699- # a group with questions
700- else
701- questions = q [ :questions ] . shuffle . slice ( 0 , q [ :pick_count ] )
702- questions . each do |question |
703- question [ :points_possible ] = q [ :question_points ]
704- user_questions << generate_submission_question ( question )
705- end
706- end
707-
708- # just a question
709- else
710- user_questions << generate_submission_question ( q )
711- end
712- end
713-
714- user_questions
715- end
716-
717578 def build_submission_end_at ( submission )
718579 course = context
719580 user = submission . user
@@ -759,12 +620,21 @@ def generate_submission(user, preview=false)
759620 submission = nil
760621
761622 transaction do
623+ builder = Quizzes ::QuizQuestionBuilder . new ( {
624+ shuffle_answers : self . shuffle_answers
625+ } )
626+
762627 submission = Quizzes ::SubmissionManager . new ( self ) . find_or_create_submission ( user , preview )
763628 submission . retake
764629 submission . attempt = ( submission . attempt + 1 ) rescue 1
765630 submission . score = nil
766631 submission . fudge_points = nil
767- submission . quiz_data = build_user_questions ( preview )
632+
633+ submission . quiz_data = begin
634+ @stored_questions = nil
635+ builder . build_submission_questions ( self . id , self . stored_questions ( preview ) )
636+ end
637+
768638 submission . quiz_version = self . version_number
769639 submission . started_at = ::Time . now
770640 submission . score_before_regrade = nil
@@ -773,6 +643,7 @@ def generate_submission(user, preview=false)
773643 submission . submission_data = { }
774644 submission . workflow_state = 'preview' if preview
775645 submission . was_preview = preview
646+ submission . question_references_fixed = true
776647
777648 if preview || submission . untaken?
778649 submission . save!
@@ -797,24 +668,6 @@ def generate_submission_for_participant(quiz_participant)
797668 generate_submission quiz_participant . send ( identity ) , false
798669 end
799670
800- def prepare_answers ( question )
801- if answers = question [ :answers ]
802- if shuffle_answers && Quizzes ::Quiz . shuffleable_question_type? ( question [ :question_type ] )
803- answers . sort_by { |a | rand }
804- else
805- answers
806- end
807- end
808- end
809-
810- def prepare_matches ( question )
811- if matches = question [ :matches ]
812- # question matches should always be shuffled, regardless of the
813- # shuffle_answers option
814- matches . sort_by { |m | rand }
815- end
816- end
817-
818671 # Takes the PRE-SAVED version of the quiz and uses it to generate a
819672 # SAVED version. That is, gathers the relationship entities from
820673 # the database and uses them to populate a static version that will
0 commit comments