CSC/ECE 517 Fall 2022 - E2255. Refactor student quizzes controller.rb: Difference between revisions

From Expertiza_Wiki
Jump to navigation Jump to search
No edit summary
 
(14 intermediate revisions by the same user not shown)
Line 1: Line 1:
==About Expertiza==
==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 [https://github.com/expertiza/expertiza Expertiza].
Ruby on Rails was used to create Expertiza, a multifunctional online application for instructors and students. Instructors using Expertiza have the ability to design their own teams, classes, assignments, quizzes, and much more. However, students are also permitted to work in groups, take tests, and do tasks. Additionally, Expertiza enables students to share peer reviews, allowing them to collaborate to enhance others' learning opportunities. It is an open-source program, and Expertiza is in Github repository [https://github.com/expertiza/expertiza Expertiza].
 
==E2255. Refactoring student_quizzes_controller.rb in Expertiza==
==E2255. Refactoring student_quizzes_controller.rb in Expertiza==
This page is a description of Expertiza OSS project E2255, which is refactoring the student_quizzes_controller.rb file.
This page is a description of Expertiza OSS project E2255, which is refactoring the student_quizzes_controller.rb file.


==Controller Description==
==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.
The student_quizzes_controller is a collection of tools for designing, grading, and documenting the results of quizzes taken by students or reviewers from other teams working on the same project. Some of the problems with this controller go against fundamental Rails design tenets like the DRY principle. This controller has a few methods that belong in model classes. Code duplication is caused by some procedures that share the same code. Rewriting certain method comments is necessary.


==Files Modified==
==Files Modified==
1. app/controllers/'''student_quizzes_controller.rb''' <br>2. app/views/student_quizzes/f'''inished_quiz.html''' <br>3. app/helpers/'''student_quizzes_helper.rb'''
1. app/controllers/'''student_quizzes_controller.rb''' <br>2. app/views/student_quizzes/'''finished_quiz.html''' <br>3. app/helpers/'''student_quizzes_helper.rb'''<br>4. app/views/student_quizzes/'''review_questions.html.erb'''


==Modifications made to the existing code==
==Modifications made to the existing code==
Line 50: Line 51:
#Populating quiz score of the Participant
#Populating quiz score of the Participant
def finished_quiz
def finished_quiz
</pre>
<pre>
# check if there is any response for this map_id. This is to prevent student take same quiz twice.
</pre>
<pre>
# If there is no instance of the response of the student, create new and save.<br>
#Quiz is already taken.<br>
def record_response
</pre>
<pre>
#Get all teams of participants who created quizzes.
#Get all quizzes of the team.
#Populate all the questionnaires of quiz.
def review_questions.
</pre>
<pre>
# Get reviewer
# Get reviewed_team_response_maps based on reviewer_id
# Get team of the reviewer
# Get the latest quiz questions of the team of reviewer
</pre>
</pre>


Line 61: Line 82:
</pre>
</pre>


'''3. Modification - ''' Line 33  - @map has been renamed to @quiz_response_map to more accurately describe its function. <br>
'''3. Modification - ''' Name of function has been renamed from record_response to save_quiz_response and also renamed other variables to better understand the purpose of the function. <br>
student_quizzes_controller.rb:<br>
student_quizzes_controller.rb:<br>
Old:
Old:
<pre>
<pre>
@map = ResponseMap.find(params[:map_id])
  def record_response
@participant = AssignmentTeam.find(@map.reviewee_id).participants.first
    map = ResponseMap.find(params[:map_id])
    if map.response.empty? # If there is no instance of the response of the student, create new and save.
</pre>
</pre>
New:
New:
<pre>
<pre>
@quiz_response_map = ResponseMap.find(params[:map_id])
  def save_quiz_response
@quiz_taker = AssignmentTeam.find(@quiz_response_map.reviewee_id).participants.first
    participant_response = ResponseMap.find(params[:map_id])
    if participant_response.response.empty? # If there is no instance of the response of the student, create new and save.
</pre>
</pre>


'''4. Modification - ''' Line 34  - @participant has been changed to @quiz_taker to more accurately describe its function. <br>
'''4. Modification - '''Changed variable names to better understand what information they are storing. <br>
student_quizzes_controller.rb:<br>
student_quizzes_controller.rb:<br>
Old:
Old:
<pre>
<pre>
@participant = AssignmentTeam.find(@map.reviewee_id).participants.first
calculate_score map, response
redirect_to controller: 'student_quizzes', action: 'finished_quiz', map_id: map.id
</pre>
</pre>
New:
New:
<pre>
<pre>
quiz_taker = AssignmentTeam.find(@quiz_response_map.reviewee_id).participants.first
calculate_score participant_response, response
redirect_to controller: 'student_quizzes', action: 'finished_quiz', map_id: participant_response.id
</pre>
</pre>
finished_quiz.html.erb:<br>
config/routes.rb:<br>
Old:
Old:
<pre>
<pre>
<%  @assignment = Assignment.find(@participant.parent_id)%>
post :record_response
<%= render :partial => 'submitted_content/main', :locals => {:participant => @participant, :stage => ""} %>
</pre>
</pre>
New:
New:
<pre>
<pre>
<%  @assignment = Assignment.find(@quiz_taker.parent_id)%>
post :save_quiz_response
<%= render :partial => 'submitted_content/main', :locals => {:participant => @quiz_taker, :stage => ""} %>
</pre>
</pre>


'''5. Modification - ''' Line 39 - The method comment of self.take_quiz has been updated to describe its function <br>
'''5. Modification - ''' Changed variable names like @response to @participant_response to better understand what information they are storing. <br>
student_quizzes_controller.rb:<br>
student_quizzes_controller.rb:<br>
Old:
Old:
<pre>
<pre>
# Gives details of the list of quizzes created by other teams for current project that a reviewer can take
    @response = Response.where(map_id: params[:map_id]).first # Populating participant response
    @response_map = QuizResponseMap.find(params[:map_id]) #Populating Quiz Response
    @questions = Question.where(questionnaire_id: @response_map.reviewed_object_id) #Populating Quiz Questions
    @map = ResponseMap.find(params[:map_id]) #Populating ResponseMap
    @participant = AssignmentTeam.find(@map.reviewee_id).participants.first #Populating participant who gave the quiz
    @quiz_score = @response_map.quiz_score #Populating quiz score of the Participant
</pre>
New:
<pre>
    @participant_response = Response.where(map_id: params[:map_id]).first # Populating participant response
    @quiz_response_map = QuizResponseMap.find(params[:map_id]) #Populating Quiz Response
    @quiz_questions = Question.where(questionnaire_id: @quiz_response_map.reviewed_object_id) #Populating Quiz Questions
    response_map = ResponseMap.find(params[:map_id]) #Populating ResponseMap
    @participant = AssignmentTeam.find(response_map.reviewee_id).participants.first #Populating participant who gave the quiz
    @participant_quiz_score = @quiz_response_map.quiz_score #Populating quiz score of the Participant
</pre>
finished_quiz.html.erb:<br>
Old:
<pre>
<%=t ".quiz_score"%><%= @quiz_score %>
<% @questions.each do |question| %>
<%user_answer=Answer.where(response_id: @response.id, question_id:  question.id)%>
<%= link_to t(".back"), student_quizzes_path(:id => @response_map.reviewer_id) %>
</pre>
</pre>
New:
New:
<pre>
<pre>
# Lists all the available quizzes created by the other teams in the current project which can be attempted.
<%=t ".quiz_score"%><%= @participant_quiz_score %>
<% @quiz_questions.each do |question| %>
<%user_answer=Answer.where(response_id: @participant_response.id, question_id:  question.id)%>
<%= link_to t(".back"), student_quizzes_path(:id => @quiz_response_map.reviewer_id) %>
</pre>
</pre>


'''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. <br>
'''6. Modification - ''' Moved the Calculate_score function to the helper file from the controller, and broke it into multiple functions, which makes it easy to understand. <br>
student_quizzes_helper.rb:<br>
student_quizzes_helper.rb:<br>
New:
Old:
<pre>
<pre>
module StudentQuizzesHelper
def calculate_score(map, response)
 
  # 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)
     questionnaire = Questionnaire.find(map.reviewed_object_id)
     answers = []
     scores = []
     has_response = true
     valid = true
     questions = Question.where(questionnaire_id: questionnaire.id)
     questions = Question.where(questionnaire_id: questionnaire.id)
     questions.each do |question|
     questions.each do |question|
      score = 0
       correct_answers = QuizQuestionChoice.where(question_id: question.id, iscorrect: true)
       correct_answers = QuizQuestionChoice.where(question_id: question.id, iscorrect: true)
       ques_type = question.type
       ques_type = question.type
       if ques_type.eql? 'MultipleChoiceCheckbox'
       if ques_type.eql? 'MultipleChoiceCheckbox'
         has_response = multiple_answer_evaluation(answers, params, question, correct_answers, has_response, response)
         if params[question.id.to_s].nil?
       # TrueFalse and MultipleChoiceRadio
          valid = false
      else
        else
         has_response = single_answer_evaluation(answers, params, question, correct_answers, has_response, response)
          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_score = Answer.new comments: choice, question_id: question.id, response_id: response.id, answer: score
            valid = false unless new_score.valid?
            scores.push(new_score)
          end
        end
       else # TrueFalse and MultipleChoiceRadio
        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
        valid = false if new_score.nil? || new_score.comments.nil? || new_score.comments.empty?
        scores.push(new_score)
       end
       end
     end
     end
     if has_response
     if valid
       answers.each(&:save)
       scores.each(&:save)
       redirect_to controller: 'student_quizzes', action: 'finished_quiz', map_id: map.id
       redirect_to controller: 'student_quizzes', action: 'finished_quiz', map_id: map.id
     else
     else
Line 139: Line 204:
     end
     end
   end
   end
</pre>
New
<pre>
module StudentQuizzesHelper
    # the way 'answers' table store the results of quiz
    def calculate_score(participant_response, quiz_response)
        scores = []
        valid = true
        get_all_questions(participant_response).each do |question|
            correct_answers = QuizQuestionChoice.where(question_id: question.id, iscorrect: true)
            ques_type = question.type
            if ques_type.eql? 'MultipleChoiceCheckbox'
                valid = is_valid_checkbox(params, question, valid)
                valid = score_multiple_choice_checkbox(correct_answers, question, params, valid, scores, quiz_response)
            else # TrueFalse and MultipleChoiceRadio
                valid = score_multiple_choice_radio(correct_answers, params, question, scores, quiz_response, valid)
            end
        end
        save_scores_if_valid(valid, scores, quiz_response,participant_response)
    end
    def save_scores_if_valid(valid, scores, quiz_response,participant_response)
        if valid
            scores.each(&:save)
            redirect_to controller: 'student_quizzes', action: 'finished_quiz', map_id: participant_response.id
        else
            quiz_response.destroy
            flash[:error] = 'Please answer every question.'
            questionnaire = Questionnaire.find(participant_response.reviewed_object_id)
            redirect_to action: :take_quiz, assignment_id: params[:assignment_id], questionnaire_id: questionnaire.id, map_id: participant_response.id
        end
    end
    def get_all_questions(participant_response)
        questionnaire = Questionnaire.find(participant_response.reviewed_object_id)
        Question.where(questionnaire_id: questionnaire.id)
    end
    def is_valid_checkbox(params, question, valid)
        if params[question.id.to_s].nil?
            valid = false;
        end
        valid
    end


  # Evaluates scores for questions that contains multiple answers
    def score_multiple_choice_checkbox(correct_answers, question, params, valid, scores, quiz_response)
  def multiple_answer_evaluation(answers, params, question, correct_answers, has_response, response)
        score = 0
    score = 0
    if params[question.id.to_s].nil?
        has_response = false
      else
         params[question.id.to_s].each do |choice|
         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
            # 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|
            correct_answers.each do |correct|
            score += 1 if choice.eql? correct.txt
                score += 1 if choice.eql? correct.txt
          end
            end
         end
         end
         score = score == correct_answers.count && score == params[question.id.to_s].count ? 1 : 0
         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.
         # for MultipleChoiceCheckbox, score =1 means the quiz taker have done this question correctly, not just make select this choice correctly.
        score_checkbox(scores, params, question, valid, quiz_response, score)
    end
    def score_checkbox(scores, params, question, valid, quiz_response, score)
         params[question.id.to_s].each do |choice|
         params[question.id.to_s].each do |choice|
          new_answer = Answer.new comments: choice, question_id: question.id, response_id: response.id, answer: score
            new_score = Answer.new comments: choice, question_id: question.id, response_id: quiz_response.id, answer: score
 
            valid = false unless new_score.valid?
          has_response = false unless new_answer.valid?
            scores.push(new_score)
          answers.push(new_answer)
         end
         end
        valid
     end
     end
    return has_response
  end


  # Evaluates scores for questions that contains only single/ true or false answers
    def score_multiple_choice_radio(correct_answers, params, question, scores, quiz_response, valid)
  def single_answer_evaluation(answers, params, question, correct_answers, has_response, response)
        correct_answer = correct_answers.first
    correct_answer = correct_answers.first
        score = correct_answer.txt == params[question.id.to_s] ? 1 : 0
    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: quiz_response.id, answer: score
    new_score = Answer.new comments: params[question.id.to_s], question_id: question.id, response_id: response.id, answer: score
        valid = false if new_score.nil? || new_score.comments.nil? || new_score.comments.empty?
    has_response = false if new_score.nil? || new_score.comments.nil? || new_score.comments.empty?
        scores.push(new_score)
    answers.push(new_score)
        valid
     return has_response
     end
  end
end
</pre>


end
'''7. Modification - ''' Renamed @assignment_id to quiz_creator_user_id to clarify the values they are holding. <br>
New
<pre>
@assignment_id = params[:id]
</pre>
Old
<pre>
@quiz_creator_user_id = params[:id]
</pre>
</pre>


'''7. Modification - ''' Line 60  - A method comment has been added for record_response to describe its function <br>
'''8. Modification - '''Renamed variable names for better understanding.
student_quizzes_controller.rb:<br>
student_quizzes_controller.rb:<br>
Old:
<pre>
<pre>
# Stores the answers entered by the quiz taker and calculates the score based on the answers entered.
def review_questions
</pre>
New:
<pre>
def get_questions_created_by_id
</pre>
</pre>


'''8. Modification - ''' -graded? has been removed as it is no longer necessary.<br>
'''9. Modification - '''Updated config variable names.  <br>
 
config/routes.rb:<br>
'''9. Modification - ''' Line 72 - The Flash message has been updated. <br>
student_quizzes_controller.rb:<br>
Old:
Old:
<pre>
<pre>
flash[:error] = 'You have already taken this quiz, below are the records for your response.'
get :review_questions
</pre>
</pre>
New:
New:
<pre>
<pre>
flash[:error] = 'You have already taken this quiz. Below are the responses of your previous attempt.'
get :get_questions_created_by_id
</pre>
</pre>


'''10. Modification - ''' Line 77 - The method comment for review_questions has been updated. <br>
'''10. Modification - ''' Modified variable name from quizzes to quiz_questions. <br>
student_quizzes_controller.rb:<br>
app/controllers/student_quizzes_controller.rb <br>
Old:
Old:
<pre>
<pre>
# This method is only for quiz questionnaires, it is called when instructors click "view quiz questions" on the pop-up panel.
# Create an array of candidate quizzes for current reviewer
def self.take_quiz(assignment_id, reviewer_id)
quizzes = []
quizzes << quiz_questionnaire unless quiz_questionnaire.taken_by? reviewer
</pre>
</pre>
New:
New:
<pre>
<pre>
# This method is only for quiz questionnaires, it is called when instructors click "view quiz questions" on the pop-up panel.
  # Create an array of candidate quiz_questions for current reviewer
# 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 self.get_quiz_questionnaire(assignment_id, reviewer_id)
  quiz_questions = []
  quiz_questions << quiz_questionnaire unless quiz_questionnaire.taken_by? reviewer
</pre>
</pre>


Line 213: Line 336:
'''1'''. Log in as instructor using the credentials, username: instructor6, password: password.<br>
'''1'''. Log in as instructor using the credentials, username: instructor6, password: password.<br>
'''2'''. Select the '''Assignments''' subheading. Here, create a new assignment by entering the necessary details. Enable the '''Has Teams?''' checkbox and create. Also enable the '''Has quiz?''' checkbox.<br>
'''2'''. Select the '''Assignments''' subheading. Here, create a new assignment by entering the necessary details. Enable the '''Has Teams?''' checkbox and create. Also enable the '''Has quiz?''' checkbox.<br>
'''3'''. Navigate to the Due Dates tab and set appropriate due dates for submission and review. For Round 1: Review, enable Has Quiz using the dropdown Menu. Once you save, A '''Number of Quiz questions''' field will be visible on the Assignments page. Set this to 2 and save.<br>
'''3'''. Set the proper due dates for submission review and evaluation by going to the Due Dates page. For Round 1: Review, enable Has Quiz using the dropdown Menu. Once you save, A '''Number of Quiz questions''' field will be visible on the Assignments page. Set this to 2 and save.<br>
'''4'''. At the top of the page, there will be a notice asking you to add participants. Using the link provided, add a few students to the assignment.<br>
'''4'''. There will be a message inviting you to add participants at the top of the page. Several students should be added to the task using the given link.<br>
'''5'''. Logout as the Instructor and Login as the students. Make submissions for the assignment before the submission deadline. <br>
'''5'''. After logging out as the teacher, log back in as the pupils. Before the deadline for submissions, turn in your assignment. <br>
Note: You must create a team and set team name to be able to make a submission.<br>
Note: To submit, you must first establish a team and give it a name.<br>
'''6'''. At the bottom of the submission page, you will see a '''Create a Quiz''' link. Select that link.<br>
'''6'''. There is a link to "Create a Quiz" at the bottom of the submission page. Select that link.<br>
'''7'''. This takes you to the quiz creation page. Here you can set Radio Button MCQs, Checkbox MCQs and True/False Questions and set the correct answers.<br>
'''7'''. You are then sent to the quiz creation page. Here, you may set True/False questions, Checkbox questions, and Radio button questions along with the appropriate responses. <br> Then click "Create Quiz." Thus, the quiz is made.<br>
Then select '''Create Quiz'''. This creates the quiz.<br>
'''8'''. Log out and re-enter the assignment as a different student who has registered. submit a document in this student's place. Review the first student's work when the submission deadline has passed, then go back to the main menu. <br>
'''8'''. Logout and login as another student registered in the assignment. Make a submission as this student. Once the submission deadline has passed, review the first student's work and return to the main menu. <br>
'''9'''. The option to "Take quizzes" will be visible. Select "Request" and then choose the quiz you made. You may now take the test that the other student made here.
'''9'''. You will see the option to '''Take quizzes'''. Here, select the quiz you have created and select '''Request'''. Here, you can take the quiz created by the other student.


===Manual Testing Walkthorugh===
===Manual Testing Walkthorugh===
The video below shows the steps for manually testing the controller functionality.
The procedure for manually evaluating the controller's functioning is demonstrated in the video below.
<br> <br>
<br>  
[https://drive.google.com/file/d/1b78JR5vAPQfzn569rciZ603gRJ8eIQnS/view?usp=sharing Manual testing video]
[https://drive.google.com/file/d/1b78JR5vAPQfzn569rciZ603gRJ8eIQnS/view?usp=sharing Manual testing video]
===Testing Result Screenshot===
Below is the screenshot of the testing cases which shows that all test cases were successfully run.<br>
[[File:Testing.png|800px|Image:800 pixels]]


===Testing Video===
===Testing Video===
Originally the student_quizzes_controller did not have any test cases, so according to the documentation, we wrote some to check if the changes we introduced have broken the functionality. Our test cases have run successfully. Here is the video for the same. <br> <br>
According to the documentation, there were no test cases for the student_quizzes_controller at first, so we created some to see if the functionality had been broken by the modifications we made. Our test cases have been completed without error. Here is the corresponding video. <br> <br>
[https://drive.google.com/file/d/1Kl8f71sylFVBIKymvjWN9A_69jbwoDdP/view?usp=sharing Test Video link]
[https://drive.google.com/file/d/1Kl8f71sylFVBIKymvjWN9A_69jbwoDdP/view?usp=sharing Test Video link]


Line 236: Line 363:


==Team members==
==Team members==
Priyam Swatil Shah (pshah9@ncsu.edu <br>
Priyam Swatil Shah (pshah9@ncsu.edu) <br>
Sanay Yogesh Shah (sshah34@ncsu.edu) <br>
Sanay Yogesh Shah (sshah34@ncsu.edu) <br>
Subodh Gujar (sgujar@ncsu.edu)
Subodh Gujar (sgujar@ncsu.edu)

Latest revision as of 18:18, 27 October 2022

About Expertiza

Ruby on Rails was used to create Expertiza, a multifunctional online application for instructors and students. Instructors using Expertiza have the ability to design their own teams, classes, assignments, quizzes, and much more. However, students are also permitted to work in groups, take tests, and do tasks. Additionally, Expertiza enables students to share peer reviews, allowing them to collaborate to enhance others' learning opportunities. It is an open-source program, and Expertiza is in Github repository Expertiza.

E2255. Refactoring student_quizzes_controller.rb in Expertiza

This page is a description of Expertiza OSS project E2255, which is refactoring the student_quizzes_controller.rb file.

Controller Description

The student_quizzes_controller is a collection of tools for designing, grading, and documenting the results of quizzes taken by students or reviewers from other teams working on the same project. Some of the problems with this controller go against fundamental Rails design tenets like the DRY principle. This controller has a few methods that belong in model classes. Code duplication is caused by some procedures that share the same code. Rewriting certain method comments is necessary.

Files Modified

1. app/controllers/student_quizzes_controller.rb
2. app/views/student_quizzes/finished_quiz.html
3. app/helpers/student_quizzes_helper.rb
4. app/views/student_quizzes/review_questions.html.erb

Modifications made to the existing code

1. Modification - Added comments for custom methods in this controller explaining the purpose.

#Checks authorization for any action based on user type: Student or Teaching Assistant.
#returns quizzes to be reviewed for a participant.
#checks if logged in user is not a participant.
def index
#For Student, check if student has authorizations of reviewer & submitter.
#returns true for any action other than 'index'.
#check if user is Teaching Assistant
def action_allowed?
#Checks authorization for any action based on user type: Student or Teaching Assistant.
#returns quizzes to be reviewed for a participant.
#checks if logged in user is not a participant.
def index
# Populating Quiz Response Data.
# Populating participant response.
#Populating Quiz Response.
#Populating Quiz Questions.
#Populating ResponseMap.
#Populating participant who gave the quiz.
#Populating quiz score of the Participant
def finished_quiz
# check if there is any response for this map_id. This is to prevent student take same quiz twice.
# If there is no instance of the response of the student, create new and save.<br>
#Quiz is already taken.<br>
def record_response
#Get all teams of participants who created quizzes.
#Get all quizzes of the team.
#Populate all the questionnaires of quiz.
def review_questions.
# Get reviewer
# Get reviewed_team_response_maps based on reviewer_id
# Get team of the reviewer
# Get the latest quiz questions of the team of reviewer

2. Modification - Removed unused function from:
student_quizzes_controller.rb:

  def graded?(response, question)
    Answer.where(question_id: question.id, response_id: response.id).first
  end

3. Modification - Name of function has been renamed from record_response to save_quiz_response and also renamed other variables to better understand the purpose of the function.
student_quizzes_controller.rb:

Old:

  def record_response
    map = ResponseMap.find(params[:map_id])
    if map.response.empty? # If there is no instance of the response of the student, create new and save.

New:

  def save_quiz_response
    participant_response = ResponseMap.find(params[:map_id])
    if participant_response.response.empty? # If there is no instance of the response of the student, create new and save.

4. Modification - Changed variable names to better understand what information they are storing.
student_quizzes_controller.rb:
Old:

calculate_score map, response
redirect_to controller: 'student_quizzes', action: 'finished_quiz', map_id: map.id

New:

calculate_score participant_response, response
redirect_to controller: 'student_quizzes', action: 'finished_quiz', map_id: participant_response.id

config/routes.rb:
Old:

post :record_response

New:

post :save_quiz_response

5. Modification - Changed variable names like @response to @participant_response to better understand what information they are storing.
student_quizzes_controller.rb:
Old:

    @response = Response.where(map_id: params[:map_id]).first # Populating participant response
    @response_map = QuizResponseMap.find(params[:map_id]) #Populating Quiz Response
    @questions = Question.where(questionnaire_id: @response_map.reviewed_object_id) #Populating Quiz Questions
    @map = ResponseMap.find(params[:map_id]) #Populating ResponseMap
    @participant = AssignmentTeam.find(@map.reviewee_id).participants.first #Populating participant who gave the quiz
    @quiz_score = @response_map.quiz_score #Populating quiz score of the Participant

New:

    @participant_response = Response.where(map_id: params[:map_id]).first # Populating participant response
    @quiz_response_map = QuizResponseMap.find(params[:map_id]) #Populating Quiz Response
    @quiz_questions = Question.where(questionnaire_id: @quiz_response_map.reviewed_object_id) #Populating Quiz Questions
    response_map = ResponseMap.find(params[:map_id]) #Populating ResponseMap
    @participant = AssignmentTeam.find(response_map.reviewee_id).participants.first #Populating participant who gave the quiz
    @participant_quiz_score = @quiz_response_map.quiz_score #Populating quiz score of the Participant

finished_quiz.html.erb:
Old:

<%=t ".quiz_score"%><%= @quiz_score %>
<% @questions.each do |question| %>
<%user_answer=Answer.where(response_id: @response.id, question_id:  question.id)%>
<%= link_to t(".back"), student_quizzes_path(:id => @response_map.reviewer_id) %>

New:

<%=t ".quiz_score"%><%= @participant_quiz_score %>
<% @quiz_questions.each do |question| %>
<%user_answer=Answer.where(response_id: @participant_response.id, question_id:  question.id)%>
<%= link_to t(".back"), student_quizzes_path(:id => @quiz_response_map.reviewer_id) %>

6. Modification - Moved the Calculate_score function to the helper file from the controller, and broke it into multiple functions, which makes it easy to understand.
student_quizzes_helper.rb:
Old:

def calculate_score(map, response)
    questionnaire = Questionnaire.find(map.reviewed_object_id)
    scores = []
    valid = true
    questions = Question.where(questionnaire_id: questionnaire.id)
    questions.each do |question|
      score = 0
      correct_answers = QuizQuestionChoice.where(question_id: question.id, iscorrect: true)
      ques_type = question.type
      if ques_type.eql? 'MultipleChoiceCheckbox'
        if params[question.id.to_s].nil?
          valid = 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_score = Answer.new comments: choice, question_id: question.id, response_id: response.id, answer: score
            valid = false unless new_score.valid?
            scores.push(new_score)
          end
        end
      else # TrueFalse and MultipleChoiceRadio
        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
        valid = false if new_score.nil? || new_score.comments.nil? || new_score.comments.empty?
        scores.push(new_score)
      end
    end
    if valid
      scores.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

New

module StudentQuizzesHelper
    # the way 'answers' table store the results of quiz
    def calculate_score(participant_response, quiz_response)
        scores = []
        valid = true
        get_all_questions(participant_response).each do |question|
            correct_answers = QuizQuestionChoice.where(question_id: question.id, iscorrect: true)
            ques_type = question.type
            if ques_type.eql? 'MultipleChoiceCheckbox'
                valid = is_valid_checkbox(params, question, valid)
                valid = score_multiple_choice_checkbox(correct_answers, question, params, valid, scores, quiz_response)
            else # TrueFalse and MultipleChoiceRadio
                valid = score_multiple_choice_radio(correct_answers, params, question, scores, quiz_response, valid)
            end
        end
        save_scores_if_valid(valid, scores, quiz_response,participant_response)
    end

    def save_scores_if_valid(valid, scores, quiz_response,participant_response)
        if valid
            scores.each(&:save)
            redirect_to controller: 'student_quizzes', action: 'finished_quiz', map_id: participant_response.id
        else
            quiz_response.destroy
            flash[:error] = 'Please answer every question.'
            questionnaire = Questionnaire.find(participant_response.reviewed_object_id)
            redirect_to action: :take_quiz, assignment_id: params[:assignment_id], questionnaire_id: questionnaire.id, map_id: participant_response.id
        end
    end

    def get_all_questions(participant_response)
        questionnaire = Questionnaire.find(participant_response.reviewed_object_id)
        Question.where(questionnaire_id: questionnaire.id)
    end

    def is_valid_checkbox(params, question, valid)
        if params[question.id.to_s].nil?
            valid = false;
        end
        valid
    end

    def score_multiple_choice_checkbox(correct_answers, question, params, valid, scores, quiz_response)
        score = 0
        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.
        score_checkbox(scores, params, question, valid, quiz_response, score)
    end

    def score_checkbox(scores, params, question, valid, quiz_response, score)
        params[question.id.to_s].each do |choice|
            new_score = Answer.new comments: choice, question_id: question.id, response_id: quiz_response.id, answer: score
            valid = false unless new_score.valid?
            scores.push(new_score)
        end
        valid
    end

    def score_multiple_choice_radio(correct_answers, params, question, scores, quiz_response, valid)
        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: quiz_response.id, answer: score
        valid = false if new_score.nil? || new_score.comments.nil? || new_score.comments.empty?
        scores.push(new_score)
        valid
    end
end

7. Modification - Renamed @assignment_id to quiz_creator_user_id to clarify the values they are holding.
New

@assignment_id = params[:id]

Old

@quiz_creator_user_id = params[:id]

8. Modification - Renamed variable names for better understanding. student_quizzes_controller.rb:
Old:

 def review_questions

New:

 def get_questions_created_by_id

9. Modification - Updated config variable names.
config/routes.rb:
Old:

 get :review_questions

New:

 get :get_questions_created_by_id

10. Modification - Modified variable name from quizzes to quiz_questions.
app/controllers/student_quizzes_controller.rb
Old:

 # Create an array of candidate quizzes for current reviewer
 def self.take_quiz(assignment_id, reviewer_id)
 quizzes = []
 quizzes << quiz_questionnaire unless quiz_questionnaire.taken_by? reviewer

New:

  # Create an array of candidate quiz_questions for current reviewer
  def self.get_quiz_questionnaire(assignment_id, reviewer_id)
  quiz_questions = []
  quiz_questions << quiz_questionnaire unless quiz_questionnaire.taken_by? reviewer

Test Plan

Manual Testing

Follow these instructions to manually test the functionality of student_quizzes_controller.
1. Log in as instructor using the credentials, username: instructor6, password: password.
2. Select the Assignments subheading. Here, create a new assignment by entering the necessary details. Enable the Has Teams? checkbox and create. Also enable the Has quiz? checkbox.
3. Set the proper due dates for submission review and evaluation by going to the Due Dates page. For Round 1: Review, enable Has Quiz using the dropdown Menu. Once you save, A Number of Quiz questions field will be visible on the Assignments page. Set this to 2 and save.
4. There will be a message inviting you to add participants at the top of the page. Several students should be added to the task using the given link.
5. After logging out as the teacher, log back in as the pupils. Before the deadline for submissions, turn in your assignment.
Note: To submit, you must first establish a team and give it a name.
6. There is a link to "Create a Quiz" at the bottom of the submission page. Select that link.
7. You are then sent to the quiz creation page. Here, you may set True/False questions, Checkbox questions, and Radio button questions along with the appropriate responses.
Then click "Create Quiz." Thus, the quiz is made.
8. Log out and re-enter the assignment as a different student who has registered. submit a document in this student's place. Review the first student's work when the submission deadline has passed, then go back to the main menu.
9. The option to "Take quizzes" will be visible. Select "Request" and then choose the quiz you made. You may now take the test that the other student made here.

Manual Testing Walkthorugh

The procedure for manually evaluating the controller's functioning is demonstrated in the video below.
Manual testing video

Testing Result Screenshot

Below is the screenshot of the testing cases which shows that all test cases were successfully run.

Image:800 pixels

Testing Video

According to the documentation, there were no test cases for the student_quizzes_controller at first, so we created some to see if the functionality had been broken by the modifications we made. Our test cases have been completed without error. Here is the corresponding video.

Test Video link

Pull Request

The link for our pull request - https://github.com/expertiza/expertiza/pull/2468

Team members

Priyam Swatil Shah (pshah9@ncsu.edu)
Sanay Yogesh Shah (sshah34@ncsu.edu)
Subodh Gujar (sgujar@ncsu.edu)

Team Mentor

Neha Kotcherlakota (nkotche@ncsu.edu)