expertiza/expertiza

View on GitHub
app/controllers/quiz_questionnaires_controller.rb

Summary

Maintainability
B
6 hrs
Test Coverage
F
0%
class QuizQuestionnairesController < QuestionnairesController
  include AuthorizationHelper

  # Quiz questionnaire edit option to be allowed for student
  def action_allowed?
    if params[:action] == 'edit'
      @questionnaire = Questionnaire.find(params[:id])
      current_user_has_admin_privileges? || current_user_is_a?('Student')
    else
      current_user_has_student_privileges?
    end
  end

  # View a quiz questionnaire
  def view
    @questionnaire = Questionnaire.find(params[:id])
    @participant = Participant.find(params[:pid]) # creating an instance variable since it needs to be sent to submitted_content/edit
    render :view
  end

  def new
    valid_request = true # A request is valid if the assignment requires a quiz, the participant has a team, and that team has a topic if the assignment has a topic
    @assignment_id = params[:aid] # creating an instance variable to hold the assignment id
    @participant_id = params[:pid] # creating an instance variable to hold the participant id
    assignment = Assignment.find(@assignment_id)
    if assignment.require_quiz?
      valid_request = team_valid?(@participant_id, assignment) # check for validity of the request
    else # flash error if this assignment does not require quiz
      flash[:error] = 'This assignment is not configured to use quizzes.'
      valid_request = false
    end
    if valid_request && Questionnaire::QUESTIONNAIRE_TYPES.include?(params[:model])
      @questionnaire = QuizQuestionnaire.new
      @questionnaire.private = params[:private]
      render 'questionnaires/new_quiz'
    else
      redirect_to controller: 'submitted_content', action: 'view', id: params[:pid]
    end
  end

  # create quiz questionnaire
  def create
    valid = validate_quiz
    if valid.eql?('valid') # The value of valid could either be "valid" or a string indicating why the quiz cannot be created
      @questionnaire = QuizQuestionnaire.new(questionnaire_params)
      participant_id = params[:pid] # Gets the participant id to be used when finding team and editing submitted content
      @questionnaire.min_question_score = params[:questionnaire][:min_question_score] # 0
      @questionnaire.max_question_score = params[:questionnaire][:max_question_score] # 1

      author_team = AssignmentTeam.team(Participant.find(participant_id)) # Gets the participant's team for the assignment

      @questionnaire.instructor_id = author_team.id # for a team assignment, set the instructor id to the team_id

      if @questionnaire.min_question_score < 0 || @questionnaire.max_question_score < 0
        flash[:error] = 'Minimum and/or maximum question score cannot be less than 0.'
        redirect_back fallback_location: root_path
      elsif @questionnaire.max_question_score < @questionnaire.min_question_score
        flash[:error] = 'Maximum question score cannot be less than minimum question score.'
        redirect_back fallback_location: root_path
      else
        @successful_create = true
        save
        save_choices @questionnaire.id
        flash[:note] = 'The quiz was successfully created.' if @successful_create
        redirect_to controller: 'submitted_content', action: 'edit', id: participant_id
      end
    else
      flash[:error] = valid.to_s
      redirect_back fallback_location: root_path
    end
  end

  # edit a quiz questionnaire
  def edit
    @questionnaire = Questionnaire.find(params[:id])
    if @questionnaire.taken_by_anyone?
      flash[:error] = 'Your quiz has been taken by one or more students; you cannot edit it anymore.'
      redirect_to controller: 'submitted_content', action: 'view', id: params[:pid]
    else # quiz can be edited only if its not taken by anyone
      render :'questionnaires/edit'
    end
  end

  # save an updated quiz questionnaire to the database
  def update
    @questionnaire = Questionnaire.find(params[:id])
    if @questionnaire.nil?
      redirect_to controller: 'submitted_content', action: 'view', id: params[:pid]
      return
    end
    if params['save'] && params[:question].try(:keys)
      @questionnaire.update_attributes(questionnaire_params)
      params[:question].each_pair do |qid, _|
        @question = Question.find(qid)
        @question.txt = params[:question][qid.to_sym][:txt]
        @question.weight = params[:question_weights][qid.to_sym][:txt]
        @question.save
        @quiz_question_choices = QuizQuestionChoice.where(question_id: qid)
        question_index = 1
        @quiz_question_choices.each do |question_choice| # Updates state of each question choice for selected question
          # Call private methods to handle question types
          update_checkbox(question_choice, question_index) if @question.type == 'MultipleChoiceCheckbox'
          update_radio(question_choice, question_index) if @question.type == 'MultipleChoiceRadio'
          update_truefalse(question_choice) if @question.type == 'TrueFalse'
          question_index += 1
        end
      end
    end
    redirect_to controller: 'submitted_content', action: 'view', id: params[:pid]
  end

  # validate quiz name, questions, answers
  # Returns "valid" if there are no issues, or a string indicating why the quiz is invalid
  def validate_quiz
    num_questions = Assignment.find(params[:aid]).num_quiz_questions
    valid = 'valid'
    if params[:questionnaire][:name] == '' # questionnaire name is not specified
      valid = 'Please specify quiz name (please do not use your name or id).'
    end
    (1..num_questions).each do |i|
      break unless valid == 'valid'

      valid = validate_question(i)
    end
    valid
  end

  private

  def team_valid?(participant_id, assignment)
    team = AssignmentParticipant.find(participant_id).team
    if team.nil? # flash error if this current participant does not have a team
      flash[:error] = 'You should create or join a team first.'
      false
    elsif assignment.topics? && team.topic.nil? # flash error if this assignment has topic but current team does not have a topic
      flash[:error] = 'Your team should have a topic.'
      false
    else # the current participant is part of a team that has a topic
      true
    end
  end

  # A question is valid if it has a valid type ('TrueFalse', 'MultipleChoiceCheckbox', 'MultipleChoiceRadio') and a correct answer selected
  def validate_question(i)
    if params.key?(:question_type) && params[:question_type].key?(i.to_s) && params[:question_type][i.to_s][:type]
      # The question type is dynamic, so const_get is necessary
      type = params[:question_type][i.to_s][:type]
      @new_question = Object.const_get(type).create(txt: '', type: type, break_before: true)
      @new_question.update_attributes(txt: params[:new_question][i.to_s])
      choice_info = params[:new_choices][i.to_s][type] # choice info for one question of its type
      valid = if choice_info.nil?
                'Please select a correct answer for all questions'
              else
                @new_question.isvalid(choice_info)
              end
    else
      # A type isn't selected for a question
      valid = 'Please select a type for each question'
    end
    valid
  end

  # create multiple choice (radio or checkbox) item(s)
  def create_multchoice(question, choice_key, q_answer_choices)
    # this method combines the functionality of create_radio and create_checkbox, so that all mult choice items are create by 1 func
    question_choice = if q_answer_choices[choice_key][:iscorrect] == 1.to_s
                        QuizQuestionChoice.new(txt: q_answer_choices[choice_key][:txt], iscorrect: 'true', question_id: question.id)
                      else
                        QuizQuestionChoice.new(txt: q_answer_choices[choice_key][:txt], iscorrect: 'false', question_id: question.id)
                      end
    question_choice.save
  end

  # create true/false item
  def create_truefalse(question, choice_key, q_answer_choices)
    if q_answer_choices[1.to_s][:iscorrect] == choice_key
      question_choice = QuizQuestionChoice.new(txt: 'True', iscorrect: 'true', question_id: question.id)
      question_choice.save
      question_choice = QuizQuestionChoice.new(txt: 'False', iscorrect: 'false', question_id: question.id)
      question_choice.save
    else
      question_choice = QuizQuestionChoice.new(txt: 'True', iscorrect: 'false', question_id: question.id)
      question_choice.save
      question_choice = QuizQuestionChoice.new(txt: 'False', iscorrect: 'true', question_id: question.id)
      question_choice.save
    end
  end

  # update checkbox item
  def update_checkbox(question_choice, question_index)
    if params[:quiz_question_choices][@question.id.to_s][@question.type][question_index.to_s]
      question_choice.update_attributes(
        iscorrect: params[:quiz_question_choices][@question.id.to_s][@question.type][question_index.to_s][:iscorrect],
        txt: params[:quiz_question_choices][@question.id.to_s][@question.type][question_index.to_s][:txt]
      )
    else
      question_choice.update_attributes(
        iscorrect: '0',
        txt: params[:quiz_question_choices][question_choice.id.to_s][:txt]
      )
    end
  end

  # update radio item
  def update_radio(question_choice, question_index)
    if params[:quiz_question_choices][@question.id.to_s][@question.type][:correctindex] == question_index.to_s
      question_choice.update_attributes(
        iscorrect: '1',
        txt: params[:quiz_question_choices][@question.id.to_s][@question.type][question_index.to_s][:txt]
      )
    else
      question_choice.update_attributes(
        iscorrect: '0',
        txt: params[:quiz_question_choices][@question.id.to_s][@question.type][question_index.to_s][:txt]
      )
    end
  end

  # update true/false item
  def update_truefalse(question_choice)
    if params[:quiz_question_choices][@question.id.to_s][@question.type][1.to_s][:iscorrect] == 'True' # the statement is correct
      question_choice.txt == 'True' ? question_choice.update_attributes(iscorrect: '1') : question_choice.update_attributes(iscorrect: '0')
      # the statement is correct so "True" is the right answer
    else # the statement is not correct
      question_choice.txt == 'True' ? question_choice.update_attributes(iscorrect: '0') : question_choice.update_attributes(iscorrect: '1')
      # the statement is not correct so "False" is the right answer
    end
  end

  # save questionnaire
  def save
    @questionnaire.save!
    undo_link("Questionnaire \"#{@questionnaire.name}\" has been updated successfully. ")
  end

  # save questions
  def save_questions(questionnaire_id)
    redirect_to controller: 'questions', action: 'delete_questions', questionnaire_id: @questionnaire.id and return
    redirect_to controller: 'question', action: 'save_new_questions', questionnaire_id: @questionnaire.id, questionnaire_type: @questionnaire.type
    if params[:question]
      params[:question].each_key do |question_key|
        if params[:question][question_key][:txt].strip.empty?
          # question text is empty, delete the question
          Question.delete(question_key)
        else
          # Update existing question.
          question = Question.find(question_key)
          Rails.logger.info(question.errors.messages.inspect) unless question.update_attributes(params[:question][question_key])
        end
      end
    end
  end

  # Saves either True/False or Multiple Choice questions to a quiz questionnaire
  # Only scorable questions can be added to a quiz, but future projects could consider relaxing this constraint
  def save_choices(questionnaire_id)
    return unless params[:new_question] || params[:new_choices]

    questions = Question.where(questionnaire_id: questionnaire_id)
    question_num = 1

    questions.each do |question|
      q_type = params[:question_type][question_num.to_s][:type]
      q_answer_choices = params[:new_choices][question_num.to_s][q_type]
      q_answer_choices.each_pair do |choice_key, _| # _ is dummy variable
        question_factory(q_type, question, choice_key, q_answer_choices) # allow factory method to create appropriate question
      end
      question_num += 1
      question.weight = 1
    end
  end

  # factory method to create the appropriate question based on the question type (true/false or multiple choice)
  def question_factory(q_type, question, choice_key, q_answer_choices)
    if q_type == 'TrueFalse'
      create_truefalse(question, choice_key, q_answer_choices)
    else # create MultipleChoice of either type, rather than creating them separately based on q_type
      create_multchoice(question, choice_key, q_answer_choices)
    end
  end

  def questionnaire_params
    params.require(:questionnaire).permit(:name, :instructor_id, :private, :min_question_score,
                                          :max_question_score, :type, :display_type, :instruction_loc)
  end
end