CSC/ECE 517 Fall 2023 - E2372. Reimplement QuizQuestion and its child classes

From Expertiza_Wiki
Jump to navigation Jump to search

Objectives

There are four methods in QuizQuestion super class. edit, view_question_text, view_completed_question, and complete. These 4 (model) methods will return different html strings depends on the question type.

  • edit: What to display` - if an instructor (etc.) is creating or editing a questionnaire (questionnaires_controller.rb)
  • view_question_text: What to display if an instructor (etc.) is viewing a questionnaire (questionnaires_controller.rb)
  • complete: What to display - if a student is filling out a questionnaire (response_controller.rb)
  • view_completed_question: What to display if a student is viewing a filled-out questionnaire (response_controller.rb)

Our goals are

  1. Make a complete UML diagram for these classes with their attributes, methods, and class methods.
  2. Check all implementations of instance methods, extract duplicated codes, and put them into the highest-level class.
  3. Thoroughly test the QuizQuestion class

Overview of the Classes

quiz_question.rb

QuizQuestion is a base class used to represent a question in a quiz. It inherits from the Question class. This class is associated with QuizQuestionChoice objects through a has_many relationship, indicating that a QuizQuestion can have multiple choices. It handles the creation of HTML for different views related to a question, such as editing, completing, and viewing the question text.

Methods:

  • edit: to generate the HTML form for editing a question,
  • complete: used for completing the question in the quiz
  • view_question_text: to display the question and its choices)
  • view_completed_question: abstract method used after quiz has been submitted, and
  • isvalid: to validate the question, especially ensuring it has text

multiple_choice_radio.rb

This class extends QuizQuestion and represents a multiple-choice question where the participant can select only one answer from a set of radio buttons.

Methods:

  • edit: overridden to add HTML for a set of radio buttons that allows the user to select one choice as the correct answer; also includes text inputs for the choices' text
  • complete method: renders HTML for the user to complete this type of question in a quiz interface
  • view_completed_question: displays the correct answer, the user's answer, and visual feedback on whether the user's answer was correct
  • isvalid: extended to ensure that not only does each choice have text, but also that exactly one correct answer is selected

multiple_choice_checkbox.rb

The MultipleChoiceCheckbox class is a subclass of QuizQuestion and represents a multiple-choice question where the participant can select multiple answers through checkboxes.

Methods:

  • edit: add HTML input elements for checkboxes, allowing multiple correct answers to be selected, along with text inputs for entering the choices.
  • complete: allows users to complete the question, displaying checkboxes for them to select their answers
  • view_completed_question: shows correct answers and whether the user's selections were correct
  • isvalid: validates the question, ensures there are multiple correct answers if it's a checkbox question.

true_false.rb

This class also extends QuizQuestion and is specialized for true/false questions.

Methods:

  • edit: includes HTML radio buttons specifically for 'True' and 'False' options
  • complete: render the true/false question for the user to answer
  • view_completed_question: displays the correct answer and the user's answer
  • isvalid: checks to ensure that the question has text and that a correct answer is designated

Our Solution

  • We implemented isvalid(choice_info) in QuizQuestion.
def isvalid(choice_info)
  @valid = 'valid'
  @valid = 'Please make sure all questions have text' if txt == ''
  @valid
end
  • We factored out duplicate code in edit from the subclasses and placed it into the superclass QuizQuestion. Now edit in the subclasses inherit partially from QuizQuestion.

edit in QuizQuestion

def edit
    @quiz_question_choices = QuizQuestionChoice.where(question_id: id)

    @html = '<tr><td>'
    @html += '<textarea cols="100" name="question[' + id.to_s + '][txt]" '
    @html += 'id="question_' + id.to_s + '_txt">' + txt + '</textarea>'
    @html += '</td></tr>'

    @html += '<tr><td>'
    @html += 'Question Weight: '
    @html += '<input type="number" name="question_weights[' + id.to_s + '][txt]" '
    @html += 'id="question_wt_' + id.to_s + '_txt" '
    @html += 'value="' + weight.to_s + '" min="0" />'
    @html += '</td></tr>'
end

edit in MultipleChoiceCheckbox

def edit
    super
    # for i in 0..3
    [0, 1, 2, 3].each do |i|
      @html += '<tr><td>'

      @html += '<input type="hidden" name="quiz_question_choices[' + id.to_s + '][MultipleChoiceCheckbox][' + (i + 1).to_s + '][iscorrect]" '
      @html += 'id="quiz_question_choices_' + id.to_s + '_MultipleChoiceCheckbox_' + (i + 1).to_s + '_iscorrect" value="0" />'

      @html += '<input type="checkbox" name="quiz_question_choices[' + id.to_s + '][MultipleChoiceCheckbox][' + (i + 1).to_s + '][iscorrect]" '
      @html += 'id="quiz_question_choices_' + id.to_s + '_MultipleChoiceCheckbox_' + (i + 1).to_s + '_iscorrect" value="1" '
      @html += 'checked="checked" ' if @quiz_question_choices[i].iscorrect
      @html += '/>'

      @html += '<input type="text" name="quiz_question_choices[' + id.to_s + '][MultipleChoiceCheckbox][' + (i + 1).to_s + '][txt]" '
      @html += 'id="quiz_question_choices_' + id.to_s + '_MultipleChoiceCheckbox_' + (i + 1).to_s + '_txt" '
      @html += 'value="' + @quiz_question_choices[i].txt + '" size="40" />'

      @html += '</td></tr>'
    end

    @html.html_safe
    # safe_join(@html)
end
  • We reimplemented QuizQuestion class and child classes methods to use @html (an instance variable) instead of html.

Final Phase Planning

  • Further refactor quiz_question.rb and subclasses
  • Currently, quiz_question.rb is a subclass of question.rb, however it does not follow the Liskov Substitution Principle because the method isvalid is not present in the superclass. By refactoring isvalid into the superclass, the entire Question hierarchy can be made more maintainable.
  • Most methods of quiz_question.rb contain hard-coded HTML, which makes the code inflexible and prone to breaking, and also violates SOP. We plan to refactor the HTML building logic out of the methods to improve readability and maintainability. This would also make testing easier.

UML Diagram

Final Phase Changes

Refactoring QuizQuestion and its child classes

1. Extract Method
We applied the extract method to make the code more readable. For example, in the edit method in MultipleChoiceRadio, we created new methods and copied the relevant code fragments from edit into the new methods.

def edit
  @quiz_question_choices = QuizQuestionChoice.where(question_id: id)
  @html = create_html_content
end

Private methods:

def create_html_content
    html_content = ""
    html_content << create_textarea_row
    html_content << create_weight_input_row
    html_content
  end

def create_textarea_row
  "<tr><td><textarea cols='100' name='question[#{id}][txt]' id='question_#{id}_txt'>#{txt}</textarea></td></tr>"
end

def create_weight_input_row
  "<tr><td>Question Weight: <input type='number' name='question_weights[#{id}][txt]' id='question_wt_#{id}_txt' value='#{weight}' min='0' /></td></tr>"
end

2. Replacing += with << In Ruby, << can be used a string concatenation operator. We replaced all instances of using += to << because we are modifying an existing object html rather than creating a new object. Here's an example from QuizQuestion's view_question_text.

Before refactoring After refactoring
def view_question_text
  @html = '<b>' + txt + '</b><br />'
  @html += 'Question Type: ' + type + '<br />'
  @html += 'Question Weight: ' + weight.to_s + '<br />'
  if quiz_question_choices
    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
def view_question_text
  @html = "<b>#{txt}</b><br />"
  @html << "Question Type: #{type} <br />"
  @html << "Question Weight:#{weight.to_s} <br />"
  @html << create_choices if quiz_question_choices.present?
  @html.html_safe
end 

Private methods:

def create_choices
  choices_html = ""
  quiz_question_choices.each do |choice|
    choices_html << choice_html(choice)
  end
  choices_html << "<br />"
  choices_html
end

def choice_html(choice)
  if choice.iscorrect?
    "  - <b>#{choice.txt}</b><br /> "
  else
    "  - #{choice.txt}<br /> "
  end
end

def all_choices_have_text?(choice_info)
  choice_info.all? { |_idx, value| value[:txt].present? }
end

changes to testing hard coded html -> Nokogiri

Testing

  • Most existing Rspec tests for quiz_question.rb and child classes test for exact strings that match the HTML. These tests will break if anything about the classes are changed, therefore they are not robust tests. The tests should be refactored to test the logic of these methods rather than their exact outputs.
  • In general, create additional tests after refactoring to verify that the refactored code work as intended. This includes functional testing to incorporate any additional helper classes created during the refactoring.

Relevant Links

Github repository: https://github.com/opheliasin/expertiza

Pull request: https://github.com/expertiza/expertiza/pull/2682

Team

Mentor

Aditi Vakeel

Members

Nathan Sudduth (ncsuddut@ncsu.edu)

Ophelia Sin (oysin@ncsu.edu)

Yi Chen (ychen282@ncsu.edu)