CSC/ECE 517 Spring 2022 - E2215: Refactor student quizzes controller
About Expertiza
Expertiza is a multi-purpose web application built using Ruby on Rails for Students and Instructors. Instructors enrolled in Expertiza can create and customize classes, teams, assignments, quizzes, and many more. On the other hand, Students are also allowed to form teams, attempt quizzes, and complete assignments. Apart from that, Expertiza also allows students to provide peer reviews enabling them to work together to improve others' learning experiences. It is an open-source application and its Github repository is Expertiza.
E2215. Refactoring student_quizzes_controller.rb in Expertiza
This page is a description of Expertiza OSS project E2215, which is refactoring the student_quizzes_controller.rb file.
Controller Description
The student_quizzes_controller consists of methods involved in creating, scoring & recording responses of the quizzes taken by reviewers or students of the other teams with the same assignment. This controller has some issues that violate essential Rails design principles such as the DRY principle. There are a few methods in this controller that should have been in model classes. Some methods share code, which creates code repetition. Some method comments need to be rewritten.
Files Modified
1. app/controllers/student_quizzes_controller.rb
2. app/views/student_quizzes/finished_quiz.html
3. app/helpers/student_quizzes_helper.rb
Modifications made to the existing code
1. Modification - Added comments for custom methods in this controller explaining the purpose.
# Based on the logged in user, verifies user's authourizations and privileges def action_allowed?
# Initializes instance variables needed to fetch the necessary details of the quizzes. def index
# For the response provided, this methods displays the questions, right/wrong answers and the final score. def finished_quiz
# Lists all the available quizzes created by the other teams in the current project which can be attempted. def self.take_quiz(assignment_id, reviewer_id)
# Stores the answers entered by the quiz taker and calculates the score based on the answers entered. def record_response
# This method is only for quiz questionnaires, it is called when instructors click "view quiz questions" on the pop-up panel. # Using the current assignment id parameter, fetches all the questions for each quiz and finally lists all the answers and scores for each submission. def review_questions
# This method as whole fetches the answers provided and calculates the final scores for the quiz. # Also calls seperate methods for handling single answer/ true or false evaluations and mulitple answer evaluations for calculating score. def calculate_score(map, response)
# Evaluates scores for questions that contains only single/ true or false answers def single_answer_evaluation(answers, params, question, correct_answers, has_response, response)
# Evaluates scores for questions that contains multiple answers def multiple_answer_evaluation(answers, params, question, correct_answers, has_response, response)
2. Modification - Line 29 - .first has been changed to .last to count the score of the most recent attempt of the quiz.
student_quizzes_controller.rb:
Old:
@response = Response.where(map_id: params[:map_id]).first
New:
@response = Response.where(map_id: params[:map_id]).last
3. Modification - Line 33 - @map has been renamed to @quiz_response_map to more accurately describe its function.
student_quizzes_controller.rb:
Old:
@map = ResponseMap.find(params[:map_id]) @participant = AssignmentTeam.find(@map.reviewee_id).participants.first
New:
@quiz_response_map = ResponseMap.find(params[:map_id]) @quiz_taker = AssignmentTeam.find(@quiz_response_map.reviewee_id).participants.first
4. Modification - Line 34 - @participant has been changed to @quiz_taker to more accurately describe its function.
student_quizzes_controller.rb:
Old:
@participant = AssignmentTeam.find(@map.reviewee_id).participants.first
New:
quiz_taker = AssignmentTeam.find(@quiz_response_map.reviewee_id).participants.first
finished_quiz.html.erb:
Old:
<% @assignment = Assignment.find(@participant.parent_id)%> <%= render :partial => 'submitted_content/main', :locals => {:participant => @participant, :stage => ""} %>
New:
<% @assignment = Assignment.find(@quiz_taker.parent_id)%> <%= render :partial => 'submitted_content/main', :locals => {:participant => @quiz_taker, :stage => ""} %>
5. Modification - Line 39 - The method comment of self.take_quiz has been updated to describe its function
student_quizzes_controller.rb:
Old:
# Gives details of the list of quizzes created by other teams for current project that a reviewer can take
New:
# Lists all the available quizzes created by the other teams in the current project which can be attempted.
6. Modification - Controller Method calculate_score has been split into three separate functions since the original method is too big and is split to use the other methods to handle score calculation for Single choice / True or False questions and Multiple answer questions separately. Then all three methods have been moved to student_quizzes_helper.rb file since they are being reused.
student_quizzes_helper.rb:
New:
module StudentQuizzesHelper # This method as whole fetches the answers provided and calculates the final scores for the quiz. # Also calls seperate methods for handling single answer/ true or false evaluations and mulitple answer evaluations for calculating score. def calculate_score(map, response) questionnaire = Questionnaire.find(map.reviewed_object_id) answers = [] has_response = true questions = Question.where(questionnaire_id: questionnaire.id) questions.each do |question| correct_answers = QuizQuestionChoice.where(question_id: question.id, iscorrect: true) ques_type = question.type if ques_type.eql? 'MultipleChoiceCheckbox' has_response = multiple_answer_evaluation(answers, params, question, correct_answers, has_response, response) # TrueFalse and MultipleChoiceRadio else has_response = single_answer_evaluation(answers, params, question, correct_answers, has_response, response) end end if has_response answers.each(&:save) redirect_to controller: 'student_quizzes', action: 'finished_quiz', map_id: map.id else response.destroy flash[:error] = 'Please answer every question.' redirect_to action: :take_quiz, assignment_id: params[:assignment_id], questionnaire_id: questionnaire.id, map_id: map.id end end # Evaluates scores for questions that contains multiple answers def multiple_answer_evaluation(answers, params, question, correct_answers, has_response, response) score = 0 if params[question.id.to_s].nil? has_response = false else params[question.id.to_s].each do |choice| # loop the quiz taker's choices and see if 1)all the correct choice are checked and 2) # of quiz taker's choice matches the # of the correct choices correct_answers.each do |correct| score += 1 if choice.eql? correct.txt end end score = score == correct_answers.count && score == params[question.id.to_s].count ? 1 : 0 # for MultipleChoiceCheckbox, score =1 means the quiz taker have done this question correctly, not just make select this choice correctly. params[question.id.to_s].each do |choice| new_answer = Answer.new comments: choice, question_id: question.id, response_id: response.id, answer: score has_response = false unless new_answer.valid? answers.push(new_answer) end end return has_response end # Evaluates scores for questions that contains only single/ true or false answers def single_answer_evaluation(answers, params, question, correct_answers, has_response, response) correct_answer = correct_answers.first score = correct_answer.txt == params[question.id.to_s] ? 1 : 0 new_score = Answer.new comments: params[question.id.to_s], question_id: question.id, response_id: response.id, answer: score has_response = false if new_score.nil? || new_score.comments.nil? || new_score.comments.empty? answers.push(new_score) return has_response end end
7. Modification - Line 60 - A method comment has been added for record_response to describe its function
student_quizzes_controller.rb:
# Stores the answers entered by the quiz taker and calculates the score based on the answers entered.
8. Modification - -graded? has been removed as it is no longer necessary.
9. Modification - Line 72 - The Flash message has been updated.
student_quizzes_controller.rb:
Old:
flash[:error] = 'You have already taken this quiz, below are the records for your response.'
New:
flash[:error] = 'You have already taken this quiz. Below are the responses of your previous attempt.'
10. Modification - Line 77 - The method comment for review_questions has been updated.
student_quizzes_controller.rb:
Old:
# This method is only for quiz questionnaires, it is called when instructors click "view quiz questions" on the pop-up panel.
New:
# This method is only for quiz questionnaires, it is called when instructors click "view quiz questions" on the pop-up panel. # Using the current assignment id parameter, fetches all the questions for each quiz and finally lists all the answers and scores for each submission.