CSC/ECE 517 Spring 2023 - E2311. Reimplement QuizQuestion and its child classes

From Expertiza_Wiki
Jump to navigation Jump to search

Background

Current State

Currently, in Expertiza there is a model class called QuizQuestion which serves as a superclass of classes MultipleChoiceCheckbox, MultipleChoiceRadio, and TrueFalse.

There are four methods in the QuizQuestion superclass which return HTML strings, depending 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)
  • view_completed_question -- What to display if a student is viewing a filled-out questionnaire (response_controller.rb)
  • complete -- What to display if a student is filling out a questionnaire (response_controller.rb)

Additionally, the inheriting classes contain a method isvalid that is not in QuizQuestion.

Included below is the UML diagram of the design of the QuizQuestion and its child classes in the current state

Our Implementation Project

For this project, we are tasked with redesigning the above UML diagram and reimplementing the five methods (mentioned in the previous section) in the superclass (QuizQuestion). The reimplementation of the methods in the superclass will provide inheritances to the subclasses (MultipleChoiceCheckbox, MultipleChoiceRadio, and TrueFalse), and will use overriding/overloading to avoid type-checking.

Included below is the redesign of the UML diagram of the relationship between QuizQuestion and its child classes. As shown in the diagram, all methods are moved to the parent class. Child classes either inherit purely from the parent class' methods or extend it through overriding/overloading.

Note that the methods reimplemented in the parent class (QuizQuestion) still render HTML components to provide to the front-end (through the response_controller.rb and questionnaires_controller.rb).

Future Direction

In the future, as the Expertiza app is being migrated to the combination of Ruby API and React front-end (instead of legacy Rails), these methods will be modified to use Ruby API, and to handle and return React objects for the front-end.

Team

  • Aileen Jacob (amjacob2)
  • Anh Nguyen (anguyen9)
  • Joe Johnson (jdjohns4)
  • Mentor Jialin Cui (jcui9)

Detailed Description of Changes

isvalid

isvalid method is implemented in all subclasses but not the superclass. The majority of these implementations are repetitive. Hence, the method is reimplemented and moved to the superclass (quiz_question.rb). In the subclasses, this method is inherited ('MultipleChoiceRadio and TrueFalse) with the exception of MultipleChoiceCheckbox where the method is extended. The implementation of isvalid is shown below

# quiz_question.rb
class QuizQuestion < Question
  has_many :quiz_question_choices, class_name: 'QuizQuestionChoice', foreign_key: 'question_id', inverse_of: false, dependent: :nullify

  ... [other methods] ...
  def isvalid(choice_info)
    @valid = 'valid'
    return @valid = 'Please make sure all questions have text' if txt == ''
    @correct_count = 0
    choice_info.each_value do |value|
      if (value[:txt] == '') || value[:txt].empty? || value[:txt].nil?
        @valid = 'Please make sure every question has text for all options'
        return @valid
      end
      @correct_count += 1 if value[:iscorrect]
    end
    @valid = 'Please select a correct answer for all questions' if @correct_count.zero?
    @valid
  end
end 
# multiple_choice_checkbox.rb
class MultipleChoiceCheckbox < QuizQuestion
  ... [other methods] ...

  def isvalid(choice_info)
    super
    if @correct_count == 1
      @valid = 'A multiple-choice checkbox question should have more than one correct answer.'
    end
    @valid
  end
end 

To test isvalid several tests were written to check for the behavior and returns of the method. Since the method is extended in MultipleChoiceCheckbox, extra tests were written to make sure that the method correctly evaluate a question and its choices. Below are the tests used for isvalid method

# quiz_question_spec.rb
require 'swagger_helper'

describe QuizQuestion do
  let(:quiz_question) { QuizQuestion.new }
   ... [setup] ...
  end
 
  ... [other tests] ...

  describe '#isvalid' do
    context 'when the question and its choices have valid text' do
      it 'returns "valid"' do
        questions = { '1' => { txt: 'question text', iscorrect: true }, '2' => { txt: 'question text', iscorrect: false }, 
                      '3' => { txt: 'question text', iscorrect: false }, '4' => { txt: 'question text', iscorrect: false } }
        expect(quiz_question.isvalid(questions)).to eq('valid')
      end
    end
  end
  describe '#isvaid' do
    let(:no_text_question) {QuizQuestion.new}
    context 'when the question itself does not have txt' do
      it 'returns "Please make sure all questions have text"' do
        allow(no_text_question).to receive(:txt).and_return('')
        questions = { '1' => { txt: 'question text', iscorrect: true }, '2' => { txt: 'question text', iscorrect: false }, 
                      '3' => { txt: 'question text', iscorrect: false }, '4' => { txt: 'question text', iscorrect: false } }
        expect(no_text_question.isvalid(questions)).to eq('Please make sure all questions have text')
      end
    end
  end
  describe '#isvalid' do
    context 'when a choice does not have text' do
      it 'returns "Please make sure every question has text for all options"' do
        questions = { '1' => { txt: 'question text', iscorrect: true }, '2' => { txt: '', iscorrect: true }, 
                      '3' => { txt: 'question text', iscorrect: false }, '4' => { txt: 'question text', iscorrect: false } }
        expect(quiz_question.isvalid(questions)).to eq('Please make sure every question has text for all options')
      end
    end
  end
  describe '#isvalid' do
    context 'when no choices are correct' do
      it 'returns "Please select a correct answer for all questions"' do
        questions = { '1' => { txt: 'question text', iscorrect: false }, '2' => { txt: 'question text', iscorrect: false }, 
                      '3' => { txt: 'question text', iscorrect: false }, '4' => { txt: 'question text', iscorrect: false } }
        expect(quiz_question.isvalid(questions)).to eq('Please select a correct answer for all questions')
      end
    end
  end
end 

Extended tests for isvalid in MultipleChoiceCheckbox

# multiple_choice_checkbox+spec.rb
require 'swagger_helper'

describe MultipleChoiceCheckbox do
  let(:multiple_choice_question) { MultipleChoiceCheckbox.new }
    ... [setup] ...
  end

  ... [other tests] ...

  describe '#isvalid' do
    context 'when there is only 1 correct answer' do
      it 'returns "A multiple-choice checkbox question should have more than one correct answer."' do
        questions = { '1' => { txt: 'question text', iscorrect: true }, '2' => { txt: 'question text', iscorrect: false }, 
                      '3' => { txt: 'question text', iscorrect: false }, '4' => { txt: 'question text', iscorrect: false } }
        expect(multiple_choice_question.isvalid(questions)).to eq('A multiple-choice checkbox question should have more than one correct answer.')
      end
    end
  end

  describe '#isvalid' do
    context 'when there is more than 1 correct answer' do
      it 'returns "valid"' do
        questions = { '1' => { txt: 'question text', iscorrect: true }, '2' => { txt: 'question text', iscorrect: false }, 
                      '3' => { txt: 'question text', iscorrect: false }, '4' => { txt: 'question text', iscorrect: true } }
        expect(multiple_choice_question.isvalid(questions)).to eq('valid')
      end
    end
  end
end 

Files Modified/Created

isvalid method

  • Reimplemented the method in quiz_question.rb
  • Removed from multiple_choice_radio.rb and true_false.rb
  • Extended in multiple_choice_checkbox.rb
  • Modified quiz_question_spec.rb and multiple_choice_checkbox_spec.rb to include tests for the method.

view_question_text method

  • left as is in quiz_question.rb

complete method

  • Reimplemented the method in quiz_question.rb
  • Removed from multiple_choice_radio.rb
  • Removed from multiple_choice_checkbox.rb
  • Added test case for complete method in quiz_question_spec.rb.

view_completed_question method

  • Reimplemented the method in quiz_question.rb
  • Removed from multiple_choice_radio.rb
  • Removed from multiple_choice_checkbox.rb
  • Added test case for view_completed_question method in quiz_question_spec.rb.

Executing Tests

  • To run the test suite written for our project, use the following command:
 bundle exec rspec spec/requests/model 
  • To run individual test files (separated by model that is being tested), use the following command:
 bundle exec rspec spec/requests/model/test_file_name.rb 

Useful Links

Our Github Forked Repo