CSC/ECE 517 Fall 2016/E1633. Refactor different question types from quiz feature

From Expertiza_Wiki
Jump to navigation Jump to search

This wiki page is for the description of changes made under E1633 OSS assignment for Fall 2016, CSC/ECE 517. The objective of the project is to refactor different question types from quiz feature.

Introduction

About Expertiza

Expertiza is a web application where students can submit assignments and quizzes and also peer-review learning objects (articles, code, web sites, etc). Expertiza achieves this using a web application with a straightforward user interface developed using Ruby on Rails. The Expertiza project is supported by the National Science Foundation.

Motivation

This project provides an opportunity for students with likeminded interests to collaborate and work as a team to enhance the existing source code of expertiza so as to improve functionality, remove bugs and implement altogether new features. This in turn provides students with a chance to showcase their skills in applying various concepts and techniques such as RSpec testing, code refactoring, CRUD design, testing strategies and other object oriented practices amongst many others.

Drawbacks of the System

  • Currently, Expertiza has a quizzing feature which allow student authors to create quiz questions and “test" the peer-reviewers. The idea behind this is, if a reviewer can answer the quiz questions which were created by the author correctly, we assume that the reviewer has read the artifact carefully enough and thereby we trust the peer-review.
  • Quiz questionnaire is one sub type of questionnaire, so it should follow the design of other type of questionnaires.
  • However, there are quite a few inconsistencies lodged in the quizzing feature.
  • The number one reason that we plan to refactor the quizzing feature is that its design is not consistent with the current questions and questionnaires.
  • Repetition of source code can be observed in quite a few areas of the system, which can be avoided.
  • The system does not adhere to the Rails principle of Convention over Configuration and Ruby naming conventions are violated in certain segments of the code.

Changes made to the System

  • The logic of necessary components was moved to the model methods. The views were correspondingly changed to invoke the required model methods.
  • The different methods defined were edit, view_completed_question, complete and view_question_text.
  • The above methods generated the specified HTML content for the specified question types, namely : MultipleChoiceCheckbox, MultipleChoiceRadio, TrueFalse.

Functionalities of the new methods

  • When the user chooses the ‘edit’ option in a quiz, the edit method is called. Based on our modification of the view, the edit method is now invoked from /view/questionnaires/_quiz_questionnaire.html.erb
  • When a user tries to take a quiz by pressing on ‘begin quiz’ in the UI, the ‘complete' method is invoked by the given view : view/student_quizzes/take_quiz
  • When a user chooses the option ‘view quiz’ or ‘view quiz questions' in the system, the view_question_text method is called which renders the view : view/questionnaires/view.html.erb
  • When the user decides to choose option ‘view’ on a completed quiz, the view_ completed_question method is called on view/student_quizzes/finished_quiz

Refactoring view_question_text

The view_question_text method is called when the author of the quiz or the instructor views the quiz. The method is responsible for generating the HTML to display the question along with each of its choices. The correct choice(s) should be bolded. Prior to refactoring, this functionality was located in app/views/student_quizzes/review_questions.html.erb:

<h1 xmlns="http://www.w3.org/1999/html"> Listing of All Quiz Questions for <%= Assignment.find(@assignment_id).name %> </h1>
<table>
  <%@quiz_questionnaires.each do |questionnaire|%>
      <%author_team = Team.find(questionnaire.instructor_id)%>
      <h2> Quiz create by <%=author_team.name%></h2>

      <%topic_id = SignedUpTeam.topic_id_by_team_id(author_team.id)%>
      <%if topic_id.nil?%>
          <%topic_name=nil%>
      <%else%>
          <%topic_name = SignUpTopic.find(topic_id).topic_name%>
          <h4> Topic: <%=topic_name%></h4>
      <%end%>

      <% response_maps = QuizResponseMap.where(reviewed_object_id:questionnaire.id) %>
      <% if !response_maps.empty? %>
        <table border="2">
          <tr>
            <th> quiz taker </th>
            <% response_maps.each do |response_map| %>
              <% reviewer_paritcipant_id = response_map.reviewer_id%>
              <% reviewer_user_id = Participant.find(reviewer_paritcipant_id).user_id%>
              <td> <%=User.find(reviewer_user_id).fullname%></td>
            <%end%>
          </tr>
          <% quiz_scores = [] %>
          <tr>
            <th> quiz score </th>
            <% response_maps.each do |response_map| %>
                <% quiz_score = response_map.quiz_score%>
                <%quiz_scores.push(quiz_score) if !quiz_score.eql?('N/A')%>
                <td> <%=quiz_score%></td>
            <%end%>

          </tr>
        </table>
        <br>
        <% if quiz_scores.size!= 0 %>
          Average score for quiz takers: <%= (quiz_scores.sum / quiz_scores.size).round(2) %>
          <br><br>
        <% end %>
      <% end %>

      <%questions = questionnaire.questions%>
      <% questions.each do |question| %>
        <%= question.view_question_text%>

      <% end %><hr>
  <% end %>
</table>

This logic has been moved to the respective models multiple_choice_checkbox.rb, multiple_choice_radio.rb, true_false.rb. Now the view simply needs to call the method:

def view_question_text
  html = "<b>" + self.txt + '</b><br />'
  html += "Question Type: " + self.type + '<br />'
  if self.quiz_question_choices
    self.quiz_question_choices.each do |choices|
      html += if choices.iscorrect?
                "  - <b>" + choices.txt + "</b><br /> "
              else
                "  - " + choices.txt + "<br /> "
              end
    end
    html += '<br />'
  end
  html.html_safe
end

Refactoring edit

The edit method is called when the author creates or edits a quiz. It makes every element of the quiz editable. Prior to refactoring, the HTML was in app/views/questionnaires/_quiz_questionnaire.html.erb:

<%= error_messages_for 'questionnaire' %>

  <table>
    <tr>
      <td valign="top"><label for="questionnaire_name">Name:</label>
        <%= text_field 'questionnaire', 'name'  %>
        <%= hidden_field 'questionnaire', 'id' %>
        <%= hidden_field 'questionnaire', 'type' %>
      </td>
    </tr>
  </table>
  <br/>
  <% if @questionnaire.type == 'QuizQuestionnaire' %>        
  <!--handle a questionnaire differently if it is a quiz-->
    <table id="questions_table">
      <% if $disp_flag != 1 %>
        <tr>
          <td align=left width=300>Questions:</td>
          <%  if @questionnaire.type != 'QuizQuestionnaire' %><td align=left>weight</td><%end%>

        </tr>
      <% end %>

      <% for @question in @questionnaire.questions %>
          <% questionnum=@question.id %>
        <tr>
          <% if $disp_flag != 1 %>
            <td > <%= text_area "question[]", 'txt', :cols=>100 %></td>
          <% end %>
          <%  @quiz_question_choices = QuizQuestionChoice.where(question_id: @question.id) %>

        </tr>
        <% i=1 %>

        <% for @quiz_question_choice in @quiz_question_choices %>
          <tr>
          <td>
               
              <% if @question.type=="MultipleChoiceCheckbox" %>
                  <%= hidden_field_tag("quiz_question_choices[#{questionnum}][MultipleChoiceCheckbox][#{i}][iscorrect]",'0') %>
                  <%= check_box_tag("quiz_question_choices[#{questionnum}][MultipleChoiceCheckbox][#{i}][iscorrect]",'1', @quiz_question_choice.iscorrect) %>
                   <%= text_field_tag "quiz_question_choices[#{questionnum}][MultipleChoiceCheckbox][#{i}][txt]", @quiz_question_choice.txt, :size=>40 %>

              <% end %>

              <%if @question.type=="MultipleChoiceRadio" %>
                <%= radio_button_tag("quiz_question_choices[#{questionnum}][MultipleChoiceRadio][correctindex]", "#{i}", @quiz_question_choice.iscorrect)%>
                 <%= text_field_tag "quiz_question_choices[#{questionnum}][MultipleChoiceRadio][#{i}][txt]", @quiz_question_choice.txt, :size=>40 %>
              <% end %>

              <%if @question.type=="TrueFalse" %>
                <% if @quiz_question_choice.txt=="True"%>
                  <%= radio_button_tag("quiz_question_choices[#{questionnum}][TrueFalse][1][iscorrect]", 'True', @quiz_question_choice.iscorrect) %>   True
                <%end%>

                <% if @quiz_question_choice.txt=="False"%>
                    <%= radio_button_tag("quiz_question_choices[#{questionnum}][TrueFalse][1][iscorrect]", 'False', @quiz_question_choice.iscorrect) %>   False
                <%end%>
              <% end %>
          </td>
          </tr>
          <% i+=1 %>
        <%end %>

      <% end %>
    </table> 
  <% end %>
<BR/>


After refactoring, the same file looked like:

<%= error_messages_for 'questionnaire' %>

  <table>
    <tr>
      <td valign="top"><label for="questionnaire_name">Name:</label>
        <%= text_field 'questionnaire', 'name'  %>
        <%= hidden_field 'questionnaire', 'id' %>
        <%= hidden_field 'questionnaire', 'type' %>
      </td>
    </tr>
  </table>
  <br/>
<% if @questionnaire.type == 'QuizQuestionnaire' %>
    <!--handle a questionnaire differently if it is a quiz-->
    <table id="questions_table">
      <% if $disp_flag != 1 %>
          <tr>
            <td align=left width=300>Questions:</td>
            <%  if @questionnaire.type != 'QuizQuestionnaire' %><td align=left>weight</td><%end%>

          </tr>
      <% end %>

      <% for @question in @questionnaire.questions %>
          <% var=1 %>
          <%= @question.edit(var) %>
      <% end %>
    </table>
<% end %>
<br/>

Refactoring complete

Logic is written in multiple_choice_checkbox model for multiple choice checkbox question type. Similarly, same process is followed for remaining question types. To begin a quiz, complete is called from the model.

    @question = self
    html = ""
    html += label_tag("#{question.id}", question.txt) +""
    quiz_question_choices = QuizQuestionChoice.where(question_id: @question.id)
    quiz_question_choices.each do |choice|
      if answer=="view"
        html += check_box_tag ("#{question.id}[]", "#{choice.txt}", choice.iscorrect)
        html += label_tag("#{choice.txt}", choice.txt) + ""
      end
      if answer=="take"
        html += check_box_tag ("#{question.id}[]", "#{choice.txt}")
        html += label_tag("#{choice.txt}", choice.txt) + ""
      end
      html.html_safe
    end
   end

Refactoring view_completed_question

The logic for viewing completed question and their answers was written in the view app/views/student_quizzes/finished_quiz.html.erb.

<h2>Answers</h2>
<b>Quiz score: <%= @quiz_score %>%</b>

<%  @assignment = Assignment.find(@participant.parent_id)%>
<%= render :partial => 'submitted_content/main', :locals => {:participant => @participant, :stage => ""} %>

<br>
<% i = 1 %>
<% @questions.each do |question| %>
  <b>Question <%= i %>: </b><%= label_tag "#{question.id}", question.txt %> <br>
  <% question_type = question.type %>


  <% if question_type.eql? 'MultipleChoiceRadio' %>
    <% QuizQuestionChoice.where(question_id: question.id).each do |answer|  %>
      <% if(answer.iscorrect) %>
        <b><%= p answer.txt  %></b> -- Correct <br>
      <%else%>
        <%= p answer.txt  %><br>
      <% end %>
    <% end %>
    <br>

    <%user_answer=Answer.where(response_id: @response.id, question_id:  question.id).first%>
    Your answer is: <b><%= user_answer.comments  %></b>
    <%if user_answer.answer==1%>
      <img src="/assets/Check-icon.png"/>
    <%else%>
      <img src="/assets/delete_icon.png"/>
    <%end%>
    <br><br>

  <% elsif question_type.eql? 'MultipleChoiceCheckbox' %>
    <br>
    <% QuizQuestionChoice.where(question_id: question.id).each do |answer|  %>
      <% if(answer.iscorrect) %>
        <b><%= p answer.txt  %></b> -- Correct <br>
      <%else%>
      <% end %>
    <% end %><br>

    Your answer is:
    <% user_answers= Answer.where(response_id: @response.id, question_id: question.id)%>
    <%if user_answers.first.answer==1%>
      <img src="/assets/Check-icon.png"/>
    <%else%>
      <img src="/assets/delete_icon.png"/>
    <%end%>

    <br/>
    <% user_answers.each do |answer| %>
      <b><%= answer.comments %></b><br>
    <% end %>
    <br>

  <% elsif(question_type == 'TrueFalse') %>
    Correct Answer is: <b><%=QuizQuestionChoice.where(question_id: question.id,iscorrect: 1).first.txt%></b><br/>
    <%user_answer=Answer.where(response_id: @response.id, question_id:  question.id).first%>
    Your answer is: <b><%= user_answer.comments %></b>
    <%if user_answer.answer==1%>
      <img src="/assets/Check-icon.png"/>
    <%else%>
      <img src="/assets/delete_icon.png"/>
    <%end%>
    <br><br>
  <% end %><hr>
  <% i += 1 %>
<% end %>
<%= link_to "Back", student_quizzes_path(:id => @response_map.reviewer_id) %>

The app/views/student_quizzes/finished_quiz.html.erb was refactored by removing the logic from the view and making it clean by using a single construct which determines question type and answer type by calling view_completed_question method declared in quiz class as shown below.

<%user_answer=Answer.where(response_id: @response.id, question_id:  question.id)%>
Question <%= i %>: <%= label_tag "#{question.id}", question.txt %> 

<%= question.view_completed_question(i,user_answer) %>
<% i += 1 %>

Similarly, logic was written in models for each of the question types. Following code snippet shows view_completed_question for true/false question type.

 def view_completed_question(count, answer)
    @question = self
    html=""
    html+= "Correct Answer is: "
    html+= QuizQuestionChoice.where(question_id: @question.id,iscorrect: 1).first.txt
    html+= ""
    html+= "Your answer is: "
    html+= answer.first.comments + ""
    if(answer.first.answer == 1)
      html+= "<img src=/assets/Check-icon.png/>"
    else
      html+= "<img src=/assets/delete_icon.png/>"
    end
    html+= ""
 end

Testing the User Interface

Since this project involved code refactoring, no new functionality was added. But here are tests for each method to verify they are working correctly.

view_question_text

  1. Login as a student.
  2. Select a quiz assignment.
  3. Select "Your Work"
  4. First you will have to create a quiz by clicking on the link "Create Quiz".
  5. Select "View quiz".
  6. You should see each question, followed by the choices. The correct choice(s) will be in Bold.

edit

  1. Login as a student.
  2. Select a Quiz Assignment.
  3. Select "Your Work"
  4. If you haven't created a quiz yet, go ahead and create one by clicking the "Create Quiz" link.
  5. Select "Edit quiz".
  6. You should see each question, followed by the choices, each in an editable text field. The correct choice(s) will be checked.

complete

  1. Login as a student.
  2. Select an assignment that has quizzing enabled (e.g. Quiz Assignment).
  3. Select "Your Work"
  4. If you haven't created a quiz yet, go ahead and create one by clicking the "Create Quiz" link.
  5. Select "Take Quizzes".
  6. Click 'Begin'.
  7. You should see each question, followed by the choices. You can record your answers for each questions and then click 'submit'. Your answers will be saved.

view_completed_question

  1. Login as a student.
  2. Select an assignment that has quizzing enabled (e.g. Quiz Assignment).
  3. Select "Your Work"
  4. If you haven't created/taken a quiz yet, go ahead and create one by clicking the "Create Quiz" link and take the quiz.
  5. Find a finished quiz and click on 'View'.
  6. You should see each question, followed by the choices. The correct choice(s) will be in bold and your recorded answer will be shown.